/src/openssh/sftp-client.c
Line | Count | Source |
1 | | /* $OpenBSD: sftp-client.c,v 1.188 2026/10/01 03:11:49 djm Exp $ */ |
2 | | /* |
3 | | * Copyright (c) 2001-2004 Damien Miller <djm@openbsd.org> |
4 | | * |
5 | | * Permission to use, copy, modify, and distribute this software for any |
6 | | * purpose with or without fee is hereby granted, provided that the above |
7 | | * copyright notice and this permission notice appear in all copies. |
8 | | * |
9 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
10 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
11 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
12 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
13 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
14 | | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
15 | | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
16 | | */ |
17 | | |
18 | | /* XXX: memleaks */ |
19 | | /* XXX: signed vs unsigned */ |
20 | | /* XXX: remove all logging, only return status codes */ |
21 | | /* XXX: copy between two remote sites */ |
22 | | |
23 | | #include "includes.h" |
24 | | |
25 | | #include <sys/types.h> |
26 | | #include <sys/queue.h> |
27 | | #include <sys/stat.h> |
28 | | #include <sys/time.h> |
29 | | #include <sys/statvfs.h> |
30 | | #include <sys/uio.h> |
31 | | |
32 | | #include <dirent.h> |
33 | | #include <errno.h> |
34 | | #include <fcntl.h> |
35 | | #include <poll.h> |
36 | | #include <signal.h> |
37 | | #include <stdarg.h> |
38 | | #include <stdio.h> |
39 | | #include <stdlib.h> |
40 | | #include <string.h> |
41 | | #include <unistd.h> |
42 | | |
43 | | #include "xmalloc.h" |
44 | | #include "ssherr.h" |
45 | | #include "sshbuf.h" |
46 | | #include "log.h" |
47 | | #include "atomicio.h" |
48 | | #include "progressmeter.h" |
49 | | #include "misc.h" |
50 | | #include "utf8.h" |
51 | | |
52 | | #include "sftp.h" |
53 | | #include "sftp-common.h" |
54 | | #include "sftp-client.h" |
55 | | |
56 | | extern volatile sig_atomic_t interrupted; |
57 | | extern int showprogress; |
58 | | |
59 | | /* Default size of buffer for up/download (fix sftp.1 scp.1 if changed) */ |
60 | 13.9k | #define DEFAULT_COPY_BUFLEN 32768 |
61 | | |
62 | | /* Default number of concurrent xfer requests (fix sftp.1 scp.1 if changed) */ |
63 | 13.9k | #define DEFAULT_NUM_REQUESTS 64 |
64 | | |
65 | | /* Minimum amount of data to read at a time */ |
66 | | #define MIN_READ_SIZE 512 |
67 | | |
68 | | /* Maximum depth to descend in directory trees */ |
69 | 0 | #define MAX_DIR_DEPTH 64 |
70 | | |
71 | | /* Directory separator characters */ |
72 | | #ifdef HAVE_CYGWIN |
73 | | # define SFTP_DIRECTORY_CHARS "/\\" |
74 | | #else /* HAVE_CYGWIN */ |
75 | 29 | # define SFTP_DIRECTORY_CHARS "/" |
76 | | #endif /* HAVE_CYGWIN */ |
77 | | |
78 | | struct sftp_conn { |
79 | | int fd_in; |
80 | | int fd_out; |
81 | | u_int download_buflen; |
82 | | u_int upload_buflen; |
83 | | u_int num_requests; |
84 | | u_int version; |
85 | | u_int msg_id; |
86 | 1.16k | #define SFTP_EXT_POSIX_RENAME 0x00000001 |
87 | 1.16k | #define SFTP_EXT_STATVFS 0x00000002 |
88 | 0 | #define SFTP_EXT_FSTATVFS 0x00000004 |
89 | 0 | #define SFTP_EXT_HARDLINK 0x00000008 |
90 | 0 | #define SFTP_EXT_FSYNC 0x00000010 |
91 | 0 | #define SFTP_EXT_LSETSTAT 0x00000020 |
92 | 15.0k | #define SFTP_EXT_LIMITS 0x00000040 |
93 | 1.16k | #define SFTP_EXT_PATH_EXPAND 0x00000080 |
94 | 0 | #define SFTP_EXT_COPY_DATA 0x00000100 |
95 | 0 | #define SFTP_EXT_GETUSERSGROUPS_BY_ID 0x00000200 |
96 | | u_int exts; |
97 | | uint64_t limit_kbps; |
98 | | struct bwlimit bwlimit_in, bwlimit_out; |
99 | | }; |
100 | | |
101 | | /* Tracks in-progress requests during file transfers */ |
102 | | struct request { |
103 | | u_int id; |
104 | | size_t len; |
105 | | uint64_t offset; |
106 | | TAILQ_ENTRY(request) tq; |
107 | | }; |
108 | | TAILQ_HEAD(requests, request); |
109 | | |
110 | | static u_char * |
111 | | get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, |
112 | | const char *errfmt, ...) __attribute__((format(printf, 4, 5))); |
113 | | |
114 | | static struct request * |
115 | | request_enqueue(struct requests *requests, u_int id, size_t len, |
116 | | uint64_t offset) |
117 | 0 | { |
118 | 0 | struct request *req; |
119 | |
|
120 | 0 | req = xcalloc(1, sizeof(*req)); |
121 | 0 | req->id = id; |
122 | 0 | req->len = len; |
123 | 0 | req->offset = offset; |
124 | 0 | TAILQ_INSERT_TAIL(requests, req, tq); |
125 | 0 | return req; |
126 | 0 | } |
127 | | |
128 | | static struct request * |
129 | | request_find(struct requests *requests, u_int id) |
130 | 0 | { |
131 | 0 | struct request *req; |
132 | |
|
133 | 0 | for (req = TAILQ_FIRST(requests); |
134 | 0 | req != NULL && req->id != id; |
135 | 0 | req = TAILQ_NEXT(req, tq)) |
136 | 0 | ; |
137 | 0 | return req; |
138 | 0 | } |
139 | | |
140 | | static int |
141 | | sftpio(void *_bwlimit, size_t amount) |
142 | 77.0k | { |
143 | 77.0k | struct bwlimit *bwlimit = (struct bwlimit *)_bwlimit; |
144 | | |
145 | 77.0k | refresh_progress_meter(0); |
146 | 77.0k | if (bwlimit != NULL) |
147 | 0 | bandwidth_limit(bwlimit, amount); |
148 | 77.0k | return 0; |
149 | 77.0k | } |
150 | | |
151 | | static void |
152 | | send_msg(struct sftp_conn *conn, struct sshbuf *m) |
153 | 26.0k | { |
154 | 26.0k | u_char mlen[4]; |
155 | 26.0k | struct iovec iov[2]; |
156 | | |
157 | 26.0k | if (sshbuf_len(m) > SFTP_MAX_MSG_LENGTH) |
158 | 0 | fatal("Outbound message too long %zu", sshbuf_len(m)); |
159 | | |
160 | | /* Send length first */ |
161 | 26.0k | put_u32(mlen, sshbuf_len(m)); |
162 | 26.0k | iov[0].iov_base = mlen; |
163 | 26.0k | iov[0].iov_len = sizeof(mlen); |
164 | 26.0k | iov[1].iov_base = (u_char *)sshbuf_ptr(m); |
165 | 26.0k | iov[1].iov_len = sshbuf_len(m); |
166 | | |
167 | 26.0k | if (atomiciov6(writev, conn->fd_out, iov, 2, sftpio, |
168 | 26.0k | conn->limit_kbps > 0 ? &conn->bwlimit_out : NULL) != |
169 | 26.0k | sshbuf_len(m) + sizeof(mlen)) |
170 | 0 | fatal("Couldn't send packet: %s", strerror(errno)); |
171 | | |
172 | 26.0k | sshbuf_reset(m); |
173 | 26.0k | } |
174 | | |
175 | | static void |
176 | | get_msg_extended(struct sftp_conn *conn, struct sshbuf *m, int initial) |
177 | 26.0k | { |
178 | 26.0k | u_int msg_len; |
179 | 26.0k | u_char *p; |
180 | 26.0k | int r; |
181 | | |
182 | 26.0k | sshbuf_reset(m); |
183 | 26.0k | if ((r = sshbuf_reserve(m, 4, &p)) != 0) |
184 | 0 | fatal_fr(r, "reserve"); |
185 | 26.0k | if (atomicio6(read, conn->fd_in, p, 4, sftpio, |
186 | 26.0k | conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) != 4) { |
187 | 158 | if (errno == EPIPE || errno == ECONNRESET) |
188 | 158 | fatal("Connection closed"); |
189 | 0 | else |
190 | 0 | fatal("Couldn't read packet: %s", strerror(errno)); |
191 | 158 | } |
192 | | |
193 | 25.8k | if ((r = sshbuf_get_u32(m, &msg_len)) != 0) |
194 | 0 | fatal_fr(r, "sshbuf_get_u32"); |
195 | 25.8k | if (msg_len > SFTP_MAX_MSG_LENGTH) { |
196 | 360 | do_log2(initial ? SYSLOG_LEVEL_ERROR : SYSLOG_LEVEL_FATAL, |
197 | 360 | "Received message too long %u", msg_len); |
198 | 360 | fatal("Ensure the remote shell produces no output " |
199 | 360 | "for non-interactive sessions."); |
200 | 360 | } |
201 | | |
202 | 25.5k | if ((r = sshbuf_reserve(m, msg_len, &p)) != 0) |
203 | 0 | fatal_fr(r, "reserve"); |
204 | 25.5k | if (atomicio6(read, conn->fd_in, p, msg_len, sftpio, |
205 | 25.5k | conn->limit_kbps > 0 ? &conn->bwlimit_in : NULL) |
206 | 25.5k | != msg_len) { |
207 | 615 | if (errno == EPIPE) |
208 | 615 | fatal("Connection closed"); |
209 | 0 | else |
210 | 0 | fatal("Read packet: %s", strerror(errno)); |
211 | 615 | } |
212 | 25.5k | } |
213 | | |
214 | | static void |
215 | | get_msg(struct sftp_conn *conn, struct sshbuf *m) |
216 | 12.1k | { |
217 | 12.1k | get_msg_extended(conn, m, 0); |
218 | 12.1k | } |
219 | | |
220 | | static void |
221 | | send_string_request(struct sftp_conn *conn, u_int id, u_int code, const char *s, |
222 | | u_int len) |
223 | 6.96k | { |
224 | 6.96k | struct sshbuf *msg; |
225 | 6.96k | int r; |
226 | | |
227 | 6.96k | if ((msg = sshbuf_new()) == NULL) |
228 | 0 | fatal_f("sshbuf_new failed"); |
229 | 6.96k | if ((r = sshbuf_put_u8(msg, code)) != 0 || |
230 | 6.96k | (r = sshbuf_put_u32(msg, id)) != 0 || |
231 | 6.96k | (r = sshbuf_put_string(msg, s, len)) != 0) |
232 | 0 | fatal_fr(r, "compose"); |
233 | 6.96k | send_msg(conn, msg); |
234 | 6.96k | debug3("Sent message fd %d T:%u I:%u", conn->fd_out, code, id); |
235 | 6.96k | sshbuf_free(msg); |
236 | 6.96k | } |
237 | | |
238 | | static void |
239 | | send_string_attrs_request(struct sftp_conn *conn, u_int id, u_int code, |
240 | | const void *s, u_int len, Attrib *a) |
241 | 1.16k | { |
242 | 1.16k | struct sshbuf *msg; |
243 | 1.16k | int r; |
244 | | |
245 | 1.16k | if ((msg = sshbuf_new()) == NULL) |
246 | 0 | fatal_f("sshbuf_new failed"); |
247 | 1.16k | if ((r = sshbuf_put_u8(msg, code)) != 0 || |
248 | 1.16k | (r = sshbuf_put_u32(msg, id)) != 0 || |
249 | 1.16k | (r = sshbuf_put_string(msg, s, len)) != 0 || |
250 | 1.16k | (r = encode_attrib(msg, a)) != 0) |
251 | 0 | fatal_fr(r, "compose"); |
252 | 1.16k | send_msg(conn, msg); |
253 | 1.16k | debug3("Sent message fd %d T:%u I:%u F:0x%04x M:%05o", |
254 | 1.16k | conn->fd_out, code, id, a->flags, a->perm); |
255 | 1.16k | sshbuf_free(msg); |
256 | 1.16k | } |
257 | | |
258 | | static u_int |
259 | | get_status(struct sftp_conn *conn, u_int expected_id) |
260 | 5.93k | { |
261 | 5.93k | struct sshbuf *msg; |
262 | 5.93k | u_char type; |
263 | 5.93k | u_int id, status; |
264 | 5.93k | int r; |
265 | | |
266 | 5.93k | if ((msg = sshbuf_new()) == NULL) |
267 | 0 | fatal_f("sshbuf_new failed"); |
268 | 5.93k | get_msg(conn, msg); |
269 | 5.93k | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
270 | 5.35k | (r = sshbuf_get_u32(msg, &id)) != 0) |
271 | 30 | fatal_fr(r, "compose"); |
272 | | |
273 | 5.90k | if (id != expected_id) |
274 | 331 | fatal("ID mismatch (%u != %u)", id, expected_id); |
275 | 5.56k | if (type != SSH2_FXP_STATUS) |
276 | 3.29k | fatal("Expected SSH2_FXP_STATUS(%u) packet, got %u", |
277 | 5.56k | SSH2_FXP_STATUS, type); |
278 | | |
279 | 2.27k | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
280 | 6 | fatal_fr(r, "parse"); |
281 | 2.26k | sshbuf_free(msg); |
282 | | |
283 | 2.26k | debug3("SSH2_FXP_STATUS %u", status); |
284 | | |
285 | 2.26k | return status; |
286 | 2.27k | } |
287 | | |
288 | | static u_char * |
289 | | get_handle(struct sftp_conn *conn, u_int expected_id, size_t *len, |
290 | | const char *errfmt, ...) |
291 | 1.16k | { |
292 | 1.16k | struct sshbuf *msg; |
293 | 1.16k | u_int id, status; |
294 | 1.16k | u_char type; |
295 | 1.16k | u_char *handle; |
296 | 1.16k | char errmsg[256]; |
297 | 1.16k | va_list args; |
298 | 1.16k | int r; |
299 | | |
300 | 1.16k | va_start(args, errfmt); |
301 | 1.16k | if (errfmt != NULL) |
302 | 1.16k | vsnprintf(errmsg, sizeof(errmsg), errfmt, args); |
303 | 1.16k | va_end(args); |
304 | | |
305 | 1.16k | if ((msg = sshbuf_new()) == NULL) |
306 | 0 | fatal_f("sshbuf_new failed"); |
307 | 1.16k | get_msg(conn, msg); |
308 | 1.16k | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
309 | 1.06k | (r = sshbuf_get_u32(msg, &id)) != 0) |
310 | 5 | fatal_fr(r, "parse"); |
311 | | |
312 | 1.15k | if (id != expected_id) |
313 | 66 | fatal("%s: ID mismatch (%u != %u)", |
314 | 1.15k | errfmt == NULL ? __func__ : errmsg, id, expected_id); |
315 | 1.08k | if (type == SSH2_FXP_STATUS) { |
316 | 332 | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
317 | 1 | fatal_fr(r, "parse status"); |
318 | 331 | if (errfmt != NULL) |
319 | 331 | error("%s: %s", errmsg, fx2txt(status)); |
320 | 331 | sshbuf_free(msg); |
321 | 331 | return(NULL); |
322 | 757 | } else if (type != SSH2_FXP_HANDLE) |
323 | 230 | fatal("%s: Expected SSH2_FXP_HANDLE(%u) packet, got %u", |
324 | 1.08k | errfmt == NULL ? __func__ : errmsg, SSH2_FXP_HANDLE, type); |
325 | | |
326 | 527 | if ((r = sshbuf_get_string(msg, &handle, len)) != 0) |
327 | 54 | fatal_fr(r, "parse handle"); |
328 | 473 | sshbuf_free(msg); |
329 | | |
330 | 473 | return handle; |
331 | 527 | } |
332 | | |
333 | | static int |
334 | | get_decode_stat(struct sftp_conn *conn, u_int expected_id, int quiet, Attrib *a) |
335 | 2.32k | { |
336 | 2.32k | struct sshbuf *msg; |
337 | 2.32k | u_int id; |
338 | 2.32k | u_char type; |
339 | 2.32k | int r; |
340 | 2.32k | Attrib attr; |
341 | | |
342 | 2.32k | if (a != NULL) |
343 | 2.32k | memset(a, '\0', sizeof(*a)); |
344 | 2.32k | if ((msg = sshbuf_new()) == NULL) |
345 | 0 | fatal_f("sshbuf_new failed"); |
346 | 2.32k | get_msg(conn, msg); |
347 | | |
348 | 2.32k | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
349 | 2.12k | (r = sshbuf_get_u32(msg, &id)) != 0) |
350 | 10 | fatal_fr(r, "parse"); |
351 | | |
352 | 2.31k | if (id != expected_id) |
353 | 132 | fatal("ID mismatch (%u != %u)", id, expected_id); |
354 | 2.17k | if (type == SSH2_FXP_STATUS) { |
355 | 664 | u_int status; |
356 | | |
357 | 664 | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
358 | 2 | fatal_fr(r, "parse status"); |
359 | 662 | if (quiet) |
360 | 662 | debug("stat remote: %s", fx2txt(status)); |
361 | 0 | else |
362 | 0 | error("stat remote: %s", fx2txt(status)); |
363 | 662 | sshbuf_free(msg); |
364 | 662 | return -1; |
365 | 1.51k | } else if (type != SSH2_FXP_ATTRS) { |
366 | 998 | fatal("Expected SSH2_FXP_ATTRS(%u) packet, got %u", |
367 | 998 | SSH2_FXP_ATTRS, type); |
368 | 998 | } |
369 | 516 | if ((r = decode_attrib(msg, &attr)) != 0) { |
370 | 296 | error_fr(r, "decode_attrib"); |
371 | 296 | sshbuf_free(msg); |
372 | 296 | return -1; |
373 | 296 | } |
374 | | /* success */ |
375 | 220 | if (a != NULL) |
376 | 24 | *a = attr; |
377 | 220 | debug3("Received stat reply T:%u I:%u F:0x%04x M:%05o", |
378 | 220 | type, id, attr.flags, attr.perm); |
379 | 220 | sshbuf_free(msg); |
380 | | |
381 | 220 | return 0; |
382 | 516 | } |
383 | | |
384 | | static int |
385 | | get_decode_statvfs(struct sftp_conn *conn, struct sftp_statvfs *st, |
386 | | u_int expected_id, int quiet) |
387 | 0 | { |
388 | 0 | struct sshbuf *msg; |
389 | 0 | u_char type; |
390 | 0 | u_int id; |
391 | 0 | uint64_t flag; |
392 | 0 | int r; |
393 | |
|
394 | 0 | if ((msg = sshbuf_new()) == NULL) |
395 | 0 | fatal_f("sshbuf_new failed"); |
396 | 0 | get_msg(conn, msg); |
397 | |
|
398 | 0 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
399 | 0 | (r = sshbuf_get_u32(msg, &id)) != 0) |
400 | 0 | fatal_fr(r, "parse"); |
401 | | |
402 | 0 | debug3("Received statvfs reply T:%u I:%u", type, id); |
403 | 0 | if (id != expected_id) |
404 | 0 | fatal("ID mismatch (%u != %u)", id, expected_id); |
405 | 0 | if (type == SSH2_FXP_STATUS) { |
406 | 0 | u_int status; |
407 | |
|
408 | 0 | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
409 | 0 | fatal_fr(r, "parse status"); |
410 | 0 | if (quiet) |
411 | 0 | debug("remote statvfs: %s", fx2txt(status)); |
412 | 0 | else |
413 | 0 | error("remote statvfs: %s", fx2txt(status)); |
414 | 0 | sshbuf_free(msg); |
415 | 0 | return -1; |
416 | 0 | } else if (type != SSH2_FXP_EXTENDED_REPLY) { |
417 | 0 | fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", |
418 | 0 | SSH2_FXP_EXTENDED_REPLY, type); |
419 | 0 | } |
420 | | |
421 | 0 | memset(st, 0, sizeof(*st)); |
422 | 0 | if ((r = sshbuf_get_u64(msg, &st->f_bsize)) != 0 || |
423 | 0 | (r = sshbuf_get_u64(msg, &st->f_frsize)) != 0 || |
424 | 0 | (r = sshbuf_get_u64(msg, &st->f_blocks)) != 0 || |
425 | 0 | (r = sshbuf_get_u64(msg, &st->f_bfree)) != 0 || |
426 | 0 | (r = sshbuf_get_u64(msg, &st->f_bavail)) != 0 || |
427 | 0 | (r = sshbuf_get_u64(msg, &st->f_files)) != 0 || |
428 | 0 | (r = sshbuf_get_u64(msg, &st->f_ffree)) != 0 || |
429 | 0 | (r = sshbuf_get_u64(msg, &st->f_favail)) != 0 || |
430 | 0 | (r = sshbuf_get_u64(msg, &st->f_fsid)) != 0 || |
431 | 0 | (r = sshbuf_get_u64(msg, &flag)) != 0 || |
432 | 0 | (r = sshbuf_get_u64(msg, &st->f_namemax)) != 0) |
433 | 0 | fatal_fr(r, "parse statvfs"); |
434 | | |
435 | 0 | st->f_flag = (flag & SSH2_FXE_STATVFS_ST_RDONLY) ? ST_RDONLY : 0; |
436 | 0 | st->f_flag |= (flag & SSH2_FXE_STATVFS_ST_NOSUID) ? ST_NOSUID : 0; |
437 | |
|
438 | 0 | sshbuf_free(msg); |
439 | |
|
440 | 0 | return 0; |
441 | 0 | } |
442 | | |
443 | | struct sftp_conn * |
444 | | sftp_init(int fd_in, int fd_out, u_int transfer_buflen, u_int num_requests, |
445 | | uint64_t limit_kbps) |
446 | 13.9k | { |
447 | 13.9k | u_char type; |
448 | 13.9k | struct sshbuf *msg; |
449 | 13.9k | struct sftp_conn *ret; |
450 | 13.9k | int r; |
451 | | |
452 | 13.9k | ret = xcalloc(1, sizeof(*ret)); |
453 | 13.9k | ret->msg_id = 1; |
454 | 13.9k | ret->fd_in = fd_in; |
455 | 13.9k | ret->fd_out = fd_out; |
456 | 13.9k | ret->download_buflen = ret->upload_buflen = |
457 | 13.9k | transfer_buflen ? transfer_buflen : DEFAULT_COPY_BUFLEN; |
458 | 13.9k | ret->num_requests = |
459 | 13.9k | num_requests ? num_requests : DEFAULT_NUM_REQUESTS; |
460 | 13.9k | ret->exts = 0; |
461 | 13.9k | ret->limit_kbps = 0; |
462 | | |
463 | 13.9k | if ((msg = sshbuf_new()) == NULL) |
464 | 0 | fatal_f("sshbuf_new failed"); |
465 | 13.9k | if ((r = sshbuf_put_u8(msg, SSH2_FXP_INIT)) != 0 || |
466 | 13.9k | (r = sshbuf_put_u32(msg, SSH2_FILEXFER_VERSION)) != 0) |
467 | 0 | fatal_fr(r, "parse"); |
468 | | |
469 | 13.9k | send_msg(ret, msg); |
470 | | |
471 | 13.9k | get_msg_extended(ret, msg, 1); |
472 | | |
473 | | /* Expecting a VERSION reply */ |
474 | 13.9k | if ((r = sshbuf_get_u8(msg, &type)) != 0) |
475 | 0 | fatal_fr(r, "parse type"); |
476 | 13.9k | if (type != SSH2_FXP_VERSION) { |
477 | 0 | error("Invalid packet back from SSH2_FXP_INIT (type %u)", |
478 | 0 | type); |
479 | 0 | sshbuf_free(msg); |
480 | 0 | free(ret); |
481 | 0 | return(NULL); |
482 | 0 | } |
483 | 13.9k | if ((r = sshbuf_get_u32(msg, &ret->version)) != 0) |
484 | 0 | fatal_fr(r, "parse version"); |
485 | | |
486 | 13.9k | debug2("Remote version: %u", ret->version); |
487 | | |
488 | | /* Check for extensions */ |
489 | 13.9k | while (sshbuf_len(msg) > 0) { |
490 | 0 | char *name; |
491 | 0 | u_char *value; |
492 | 0 | size_t vlen; |
493 | 0 | int known = 0; |
494 | |
|
495 | 0 | if ((r = sshbuf_get_cstring(msg, &name, NULL)) != 0 || |
496 | 0 | (r = sshbuf_get_string(msg, &value, &vlen)) != 0) |
497 | 0 | fatal_fr(r, "parse extension"); |
498 | 0 | if (strcmp(name, "posix-rename@openssh.com") == 0 && |
499 | 0 | strcmp((char *)value, "1") == 0) { |
500 | 0 | ret->exts |= SFTP_EXT_POSIX_RENAME; |
501 | 0 | known = 1; |
502 | 0 | } else if (strcmp(name, "statvfs@openssh.com") == 0 && |
503 | 0 | strcmp((char *)value, "2") == 0) { |
504 | 0 | ret->exts |= SFTP_EXT_STATVFS; |
505 | 0 | known = 1; |
506 | 0 | } else if (strcmp(name, "fstatvfs@openssh.com") == 0 && |
507 | 0 | strcmp((char *)value, "2") == 0) { |
508 | 0 | ret->exts |= SFTP_EXT_FSTATVFS; |
509 | 0 | known = 1; |
510 | 0 | } else if (strcmp(name, "hardlink@openssh.com") == 0 && |
511 | 0 | strcmp((char *)value, "1") == 0) { |
512 | 0 | ret->exts |= SFTP_EXT_HARDLINK; |
513 | 0 | known = 1; |
514 | 0 | } else if (strcmp(name, "fsync@openssh.com") == 0 && |
515 | 0 | strcmp((char *)value, "1") == 0) { |
516 | 0 | ret->exts |= SFTP_EXT_FSYNC; |
517 | 0 | known = 1; |
518 | 0 | } else if (strcmp(name, "lsetstat@openssh.com") == 0 && |
519 | 0 | strcmp((char *)value, "1") == 0) { |
520 | 0 | ret->exts |= SFTP_EXT_LSETSTAT; |
521 | 0 | known = 1; |
522 | 0 | } else if (strcmp(name, "limits@openssh.com") == 0 && |
523 | 0 | strcmp((char *)value, "1") == 0) { |
524 | 0 | ret->exts |= SFTP_EXT_LIMITS; |
525 | 0 | known = 1; |
526 | 0 | } else if (strcmp(name, "expand-path@openssh.com") == 0 && |
527 | 0 | strcmp((char *)value, "1") == 0) { |
528 | 0 | ret->exts |= SFTP_EXT_PATH_EXPAND; |
529 | 0 | known = 1; |
530 | 0 | } else if (strcmp(name, "copy-data") == 0 && |
531 | 0 | strcmp((char *)value, "1") == 0) { |
532 | 0 | ret->exts |= SFTP_EXT_COPY_DATA; |
533 | 0 | known = 1; |
534 | 0 | } else if (strcmp(name, |
535 | 0 | "users-groups-by-id@openssh.com") == 0 && |
536 | 0 | strcmp((char *)value, "1") == 0) { |
537 | 0 | ret->exts |= SFTP_EXT_GETUSERSGROUPS_BY_ID; |
538 | 0 | known = 1; |
539 | 0 | } |
540 | 0 | if (known) { |
541 | 0 | debug2("Server supports extension \"%s\" revision %s", |
542 | 0 | name, value); |
543 | 0 | } else { |
544 | 0 | debug2("Unrecognised server extension \"%s\"", name); |
545 | 0 | } |
546 | 0 | free(name); |
547 | 0 | free(value); |
548 | 0 | } |
549 | | |
550 | 13.9k | sshbuf_free(msg); |
551 | | |
552 | | /* Query the server for its limits */ |
553 | 13.9k | if (ret->exts & SFTP_EXT_LIMITS) { |
554 | 0 | struct sftp_limits limits; |
555 | 0 | if (sftp_get_limits(ret, &limits) != 0) |
556 | 0 | fatal_f("limits failed"); |
557 | | |
558 | | /* If the caller did not specify, find a good value */ |
559 | 0 | if (transfer_buflen == 0) { |
560 | 0 | ret->download_buflen = MINIMUM(limits.read_length, |
561 | 0 | SFTP_MAX_MSG_LENGTH - 1024); |
562 | 0 | ret->upload_buflen = MINIMUM(limits.write_length, |
563 | 0 | SFTP_MAX_MSG_LENGTH - 1024); |
564 | 0 | ret->download_buflen = MAXIMUM(ret->download_buflen, 64); |
565 | 0 | ret->upload_buflen = MAXIMUM(ret->upload_buflen, 64); |
566 | 0 | debug3("server upload/download buffer sizes " |
567 | 0 | "%llu / %llu; using %u / %u", |
568 | 0 | (unsigned long long)limits.write_length, |
569 | 0 | (unsigned long long)limits.read_length, |
570 | 0 | ret->upload_buflen, ret->download_buflen); |
571 | 0 | } |
572 | 0 | } |
573 | | |
574 | | /* Some filexfer v.0 servers don't support large packets */ |
575 | 13.9k | if (ret->version == 0) { |
576 | 0 | ret->download_buflen = MINIMUM(ret->download_buflen, 20480); |
577 | 0 | ret->upload_buflen = MINIMUM(ret->upload_buflen, 20480); |
578 | 0 | } |
579 | | |
580 | 13.9k | ret->limit_kbps = limit_kbps; |
581 | 13.9k | if (ret->limit_kbps > 0) { |
582 | 0 | bandwidth_limit_init(&ret->bwlimit_in, ret->limit_kbps, |
583 | 0 | ret->download_buflen); |
584 | 0 | bandwidth_limit_init(&ret->bwlimit_out, ret->limit_kbps, |
585 | 0 | ret->upload_buflen); |
586 | 0 | } |
587 | | |
588 | 13.9k | return ret; |
589 | 13.9k | } |
590 | | |
591 | | void |
592 | | sftp_free(struct sftp_conn *conn) |
593 | 5.76k | { |
594 | 5.76k | if (conn == NULL) |
595 | 0 | return; |
596 | 5.76k | freezero(conn, sizeof(*conn)); |
597 | 5.76k | } |
598 | | |
599 | | u_int |
600 | | sftp_proto_version(struct sftp_conn *conn) |
601 | 0 | { |
602 | 0 | return conn->version; |
603 | 0 | } |
604 | | |
605 | | int |
606 | | sftp_get_limits(struct sftp_conn *conn, struct sftp_limits *limits) |
607 | 1.16k | { |
608 | 1.16k | u_int id, msg_id; |
609 | 1.16k | u_char type; |
610 | 1.16k | struct sshbuf *msg; |
611 | 1.16k | int r; |
612 | | |
613 | 1.16k | if ((conn->exts & SFTP_EXT_LIMITS) == 0) { |
614 | 1.16k | error("Server does not support limits@openssh.com extension"); |
615 | 1.16k | return -1; |
616 | 1.16k | } |
617 | | |
618 | 0 | if ((msg = sshbuf_new()) == NULL) |
619 | 0 | fatal_f("sshbuf_new failed"); |
620 | | |
621 | 0 | id = conn->msg_id++; |
622 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
623 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
624 | 0 | (r = sshbuf_put_cstring(msg, "limits@openssh.com")) != 0) |
625 | 0 | fatal_fr(r, "compose"); |
626 | 0 | send_msg(conn, msg); |
627 | 0 | debug3("Sent message limits@openssh.com I:%u", id); |
628 | |
|
629 | 0 | get_msg(conn, msg); |
630 | |
|
631 | 0 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
632 | 0 | (r = sshbuf_get_u32(msg, &msg_id)) != 0) |
633 | 0 | fatal_fr(r, "parse"); |
634 | | |
635 | 0 | debug3("Received limits reply T:%u I:%u", type, msg_id); |
636 | 0 | if (id != msg_id) |
637 | 0 | fatal("ID mismatch (%u != %u)", msg_id, id); |
638 | 0 | if (type != SSH2_FXP_EXTENDED_REPLY) { |
639 | 0 | debug_f("expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", |
640 | 0 | SSH2_FXP_EXTENDED_REPLY, type); |
641 | | /* Disable the limits extension */ |
642 | 0 | conn->exts &= ~SFTP_EXT_LIMITS; |
643 | 0 | sshbuf_free(msg); |
644 | 0 | return -1; |
645 | 0 | } |
646 | | |
647 | 0 | memset(limits, 0, sizeof(*limits)); |
648 | 0 | if ((r = sshbuf_get_u64(msg, &limits->packet_length)) != 0 || |
649 | 0 | (r = sshbuf_get_u64(msg, &limits->read_length)) != 0 || |
650 | 0 | (r = sshbuf_get_u64(msg, &limits->write_length)) != 0 || |
651 | 0 | (r = sshbuf_get_u64(msg, &limits->open_handles)) != 0) |
652 | 0 | fatal_fr(r, "parse limits"); |
653 | | |
654 | 0 | sshbuf_free(msg); |
655 | |
|
656 | 0 | return 0; |
657 | 0 | } |
658 | | |
659 | | int |
660 | | sftp_close(struct sftp_conn *conn, const u_char *handle, u_int handle_len) |
661 | 130 | { |
662 | 130 | u_int id, status; |
663 | 130 | struct sshbuf *msg; |
664 | 130 | int r; |
665 | | |
666 | 130 | if ((msg = sshbuf_new()) == NULL) |
667 | 0 | fatal_f("sshbuf_new failed"); |
668 | | |
669 | 130 | id = conn->msg_id++; |
670 | 130 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_CLOSE)) != 0 || |
671 | 130 | (r = sshbuf_put_u32(msg, id)) != 0 || |
672 | 130 | (r = sshbuf_put_string(msg, handle, handle_len)) != 0) |
673 | 0 | fatal_fr(r, "parse"); |
674 | 130 | send_msg(conn, msg); |
675 | 130 | debug3("Sent message SSH2_FXP_CLOSE I:%u", id); |
676 | | |
677 | 130 | status = get_status(conn, id); |
678 | 130 | if (status != SSH2_FX_OK) |
679 | 48 | error("close remote: %s", fx2txt(status)); |
680 | | |
681 | 130 | sshbuf_free(msg); |
682 | | |
683 | 130 | return status == SSH2_FX_OK ? 0 : -1; |
684 | 130 | } |
685 | | |
686 | | |
687 | | static int |
688 | | sftp_lsreaddir(struct sftp_conn *conn, const char *path, int print_flag, |
689 | | SFTP_DIRENT ***dir) |
690 | 1.16k | { |
691 | 1.16k | struct sshbuf *msg; |
692 | 1.16k | u_int count, id, i, expected_id, ents = 0; |
693 | 1.16k | size_t handle_len; |
694 | 1.16k | u_char type, *handle; |
695 | 1.16k | int status = SSH2_FX_FAILURE; |
696 | 1.16k | int r; |
697 | | |
698 | 1.16k | if (dir) |
699 | 1.16k | *dir = NULL; |
700 | | |
701 | 1.16k | id = conn->msg_id++; |
702 | | |
703 | 1.16k | if ((msg = sshbuf_new()) == NULL) |
704 | 0 | fatal_f("sshbuf_new failed"); |
705 | 1.16k | if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPENDIR)) != 0 || |
706 | 1.16k | (r = sshbuf_put_u32(msg, id)) != 0 || |
707 | 1.16k | (r = sshbuf_put_cstring(msg, path)) != 0) |
708 | 0 | fatal_fr(r, "compose OPENDIR"); |
709 | 1.16k | send_msg(conn, msg); |
710 | | |
711 | 1.16k | handle = get_handle(conn, id, &handle_len, |
712 | 1.16k | "remote readdir(\"%s\")", path); |
713 | 1.16k | if (handle == NULL) { |
714 | 331 | sshbuf_free(msg); |
715 | 331 | return -1; |
716 | 331 | } |
717 | | |
718 | 829 | if (dir) { |
719 | 375 | ents = 0; |
720 | 375 | *dir = xcalloc(1, sizeof(**dir)); |
721 | 375 | (*dir)[0] = NULL; |
722 | 375 | } |
723 | | |
724 | 911 | for (; !interrupted;) { |
725 | 378 | id = expected_id = conn->msg_id++; |
726 | | |
727 | 378 | debug3("Sending SSH2_FXP_READDIR I:%u", id); |
728 | | |
729 | 378 | sshbuf_reset(msg); |
730 | 378 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_READDIR)) != 0 || |
731 | 378 | (r = sshbuf_put_u32(msg, id)) != 0 || |
732 | 378 | (r = sshbuf_put_string(msg, handle, handle_len)) != 0) |
733 | 0 | fatal_fr(r, "compose READDIR"); |
734 | 378 | send_msg(conn, msg); |
735 | | |
736 | 378 | sshbuf_reset(msg); |
737 | | |
738 | 378 | get_msg(conn, msg); |
739 | | |
740 | 378 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
741 | 297 | (r = sshbuf_get_u32(msg, &id)) != 0) |
742 | 3 | fatal_fr(r, "parse"); |
743 | | |
744 | 375 | debug3("Received reply T:%u I:%u", type, id); |
745 | | |
746 | 375 | if (id != expected_id) |
747 | 54 | fatal("ID mismatch (%u != %u)", id, expected_id); |
748 | | |
749 | 321 | if (type == SSH2_FXP_STATUS) { |
750 | 95 | u_int rstatus; |
751 | | |
752 | 95 | if ((r = sshbuf_get_u32(msg, &rstatus)) != 0) |
753 | 1 | fatal_fr(r, "parse status"); |
754 | 94 | debug3("Received SSH2_FXP_STATUS %d", rstatus); |
755 | 94 | if (rstatus == SSH2_FX_EOF) |
756 | 33 | break; |
757 | 61 | error("Couldn't read directory: %s", fx2txt(rstatus)); |
758 | 61 | goto out; |
759 | 226 | } else if (type != SSH2_FXP_NAME) |
760 | 17 | fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", |
761 | 321 | SSH2_FXP_NAME, type); |
762 | | |
763 | 209 | if ((r = sshbuf_get_u32(msg, &count)) != 0) |
764 | 1 | fatal_fr(r, "parse count"); |
765 | 208 | if (count > SSHBUF_SIZE_MAX) |
766 | 8 | fatal_f("nonsensical number of entries"); |
767 | 200 | if (count == 0) |
768 | 1 | break; |
769 | 199 | debug3("Received %d SSH2_FXP_NAME responses", count); |
770 | 695 | for (i = 0; i < count; i++) { |
771 | 613 | char *filename, *longname; |
772 | 613 | Attrib a; |
773 | | |
774 | 613 | if ((r = sshbuf_get_cstring(msg, &filename, |
775 | 613 | NULL)) != 0 || |
776 | 541 | (r = sshbuf_get_cstring(msg, &longname, |
777 | 541 | NULL)) != 0) |
778 | 82 | fatal_fr(r, "parse filenames"); |
779 | 531 | if ((r = decode_attrib(msg, &a)) != 0) { |
780 | 35 | error_fr(r, "couldn't decode attrib"); |
781 | 35 | free(filename); |
782 | 35 | free(longname); |
783 | 35 | goto out; |
784 | 35 | } |
785 | | |
786 | 496 | if (print_flag) |
787 | 0 | mprintf("%s\n", longname); |
788 | | |
789 | | /* |
790 | | * Directory entries should never contain '/' |
791 | | * These can be used to attack recursive ops |
792 | | * (e.g. send '../../../../etc/passwd') |
793 | | */ |
794 | 496 | if (*filename == '\0' || |
795 | 496 | strpbrk(filename, SFTP_DIRECTORY_CHARS) != NULL) { |
796 | 496 | error("Server sent suspect path \"%s\" " |
797 | 496 | "during readdir of \"%s\"", filename, path); |
798 | 496 | } else if (dir) { |
799 | 0 | *dir = xreallocarray(*dir, ents + 2, sizeof(**dir)); |
800 | 0 | (*dir)[ents] = xcalloc(1, sizeof(***dir)); |
801 | 0 | (*dir)[ents]->filename = xstrdup(filename); |
802 | 0 | (*dir)[ents]->longname = xstrdup(longname); |
803 | 0 | memcpy(&(*dir)[ents]->a, &a, sizeof(a)); |
804 | 0 | (*dir)[++ents] = NULL; |
805 | 0 | } |
806 | 496 | free(filename); |
807 | 496 | free(longname); |
808 | 496 | } |
809 | 199 | } |
810 | 567 | status = 0; |
811 | | |
812 | 567 | out: |
813 | 130 | sshbuf_free(msg); |
814 | 130 | sftp_close(conn, handle, handle_len); |
815 | 130 | free(handle); |
816 | | |
817 | 130 | if (status != 0 && dir != NULL) { |
818 | | /* Don't return results on error */ |
819 | 17 | sftp_free_dirents(*dir); |
820 | 17 | *dir = NULL; |
821 | 113 | } else if (interrupted && dir != NULL && *dir != NULL) { |
822 | | /* Don't return partial matches on interrupt */ |
823 | 0 | sftp_free_dirents(*dir); |
824 | 0 | *dir = xcalloc(1, sizeof(**dir)); |
825 | 0 | **dir = NULL; |
826 | 0 | } |
827 | | |
828 | 130 | return status == SSH2_FX_OK ? 0 : -1; |
829 | 567 | } |
830 | | |
831 | | int |
832 | | sftp_readdir(struct sftp_conn *conn, const char *path, SFTP_DIRENT ***dir) |
833 | 1.16k | { |
834 | 1.16k | return sftp_lsreaddir(conn, path, 0, dir); |
835 | 1.16k | } |
836 | | |
837 | | void sftp_free_dirents(SFTP_DIRENT **s) |
838 | 49 | { |
839 | 49 | int i; |
840 | | |
841 | 49 | if (s == NULL) |
842 | 0 | return; |
843 | 49 | for (i = 0; s[i]; i++) { |
844 | 0 | free(s[i]->filename); |
845 | 0 | free(s[i]->longname); |
846 | 0 | free(s[i]); |
847 | 0 | } |
848 | 49 | free(s); |
849 | 49 | } |
850 | | |
851 | | int |
852 | | sftp_rm(struct sftp_conn *conn, const char *path) |
853 | 1.16k | { |
854 | 1.16k | u_int status, id; |
855 | | |
856 | 1.16k | debug2("Sending SSH2_FXP_REMOVE \"%s\"", path); |
857 | | |
858 | 1.16k | id = conn->msg_id++; |
859 | 1.16k | send_string_request(conn, id, SSH2_FXP_REMOVE, path, strlen(path)); |
860 | 1.16k | status = get_status(conn, id); |
861 | 1.16k | if (status != SSH2_FX_OK) |
862 | 312 | error("remote delete %s: %s", path, fx2txt(status)); |
863 | 1.16k | return status == SSH2_FX_OK ? 0 : -1; |
864 | 1.16k | } |
865 | | |
866 | | int |
867 | | sftp_mkdir(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) |
868 | 1.16k | { |
869 | 1.16k | u_int status, id; |
870 | | |
871 | 1.16k | debug2("Sending SSH2_FXP_MKDIR \"%s\"", path); |
872 | | |
873 | 1.16k | id = conn->msg_id++; |
874 | 1.16k | send_string_attrs_request(conn, id, SSH2_FXP_MKDIR, path, |
875 | 1.16k | strlen(path), a); |
876 | | |
877 | 1.16k | status = get_status(conn, id); |
878 | 1.16k | if (status != SSH2_FX_OK && print_flag) |
879 | 0 | error("remote mkdir \"%s\": %s", path, fx2txt(status)); |
880 | | |
881 | 1.16k | return status == SSH2_FX_OK ? 0 : -1; |
882 | 1.16k | } |
883 | | |
884 | | int |
885 | | sftp_mkpath(struct sftp_conn *conn, const char *path, Attrib *a, int print_flag) |
886 | 0 | { |
887 | 0 | char *slash, *tmp_path; |
888 | 0 | int done; |
889 | |
|
890 | 0 | tmp_path = xstrdup(path); |
891 | 0 | slash = tmp_path; |
892 | |
|
893 | 0 | for (;;) { |
894 | 0 | slash += strspn(slash, "/"); |
895 | 0 | slash += strcspn(slash, "/"); |
896 | |
|
897 | 0 | done = (*slash == '\0'); |
898 | 0 | *slash = '\0'; |
899 | |
|
900 | 0 | if (!sftp_remote_is_dir(conn, tmp_path)) { |
901 | 0 | if (sftp_mkdir(conn, tmp_path, a, print_flag) != 0) { |
902 | 0 | free(tmp_path); |
903 | 0 | return -1; |
904 | 0 | } |
905 | 0 | } |
906 | | |
907 | 0 | if (done) |
908 | 0 | break; |
909 | | |
910 | 0 | *slash = '/'; |
911 | 0 | } |
912 | | |
913 | 0 | free(tmp_path); |
914 | 0 | return 0; |
915 | 0 | } |
916 | | |
917 | | int |
918 | | sftp_rmdir(struct sftp_conn *conn, const char *path) |
919 | 1.16k | { |
920 | 1.16k | u_int status, id; |
921 | | |
922 | 1.16k | debug2("Sending SSH2_FXP_RMDIR \"%s\"", path); |
923 | | |
924 | 1.16k | id = conn->msg_id++; |
925 | 1.16k | send_string_request(conn, id, SSH2_FXP_RMDIR, path, |
926 | 1.16k | strlen(path)); |
927 | | |
928 | 1.16k | status = get_status(conn, id); |
929 | 1.16k | if (status != SSH2_FX_OK) |
930 | 312 | error("remote rmdir \"%s\": %s", path, fx2txt(status)); |
931 | | |
932 | 1.16k | return status == SSH2_FX_OK ? 0 : -1; |
933 | 1.16k | } |
934 | | |
935 | | int |
936 | | sftp_stat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a) |
937 | 1.16k | { |
938 | 1.16k | u_int id; |
939 | | |
940 | 1.16k | debug2("Sending SSH2_FXP_STAT \"%s\"", path); |
941 | | |
942 | 1.16k | id = conn->msg_id++; |
943 | | |
944 | 1.16k | send_string_request(conn, id, |
945 | 1.16k | conn->version == 0 ? SSH2_FXP_STAT_VERSION_0 : SSH2_FXP_STAT, |
946 | 1.16k | path, strlen(path)); |
947 | | |
948 | 1.16k | return get_decode_stat(conn, id, quiet, a); |
949 | 1.16k | } |
950 | | |
951 | | int |
952 | | sftp_lstat(struct sftp_conn *conn, const char *path, int quiet, Attrib *a) |
953 | 1.16k | { |
954 | 1.16k | u_int id; |
955 | | |
956 | 1.16k | if (conn->version == 0) { |
957 | 0 | do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO, |
958 | 0 | "Server version does not support lstat operation"); |
959 | 0 | return sftp_stat(conn, path, quiet, a); |
960 | 0 | } |
961 | | |
962 | 1.16k | id = conn->msg_id++; |
963 | 1.16k | send_string_request(conn, id, SSH2_FXP_LSTAT, path, |
964 | 1.16k | strlen(path)); |
965 | | |
966 | 1.16k | return get_decode_stat(conn, id, quiet, a); |
967 | 1.16k | } |
968 | | |
969 | | #ifdef notyet |
970 | | int |
971 | | sftp_fstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
972 | | int quiet, Attrib *a) |
973 | | { |
974 | | u_int id; |
975 | | |
976 | | debug2("Sending SSH2_FXP_FSTAT \"%s\""); |
977 | | |
978 | | id = conn->msg_id++; |
979 | | send_string_request(conn, id, SSH2_FXP_FSTAT, handle, |
980 | | handle_len); |
981 | | |
982 | | return get_decode_stat(conn, id, quiet, a); |
983 | | } |
984 | | #endif |
985 | | |
986 | | int |
987 | | sftp_setstat(struct sftp_conn *conn, const char *path, Attrib *a) |
988 | 0 | { |
989 | 0 | u_int status, id; |
990 | |
|
991 | 0 | debug2("Sending SSH2_FXP_SETSTAT \"%s\"", path); |
992 | |
|
993 | 0 | id = conn->msg_id++; |
994 | 0 | send_string_attrs_request(conn, id, SSH2_FXP_SETSTAT, path, |
995 | 0 | strlen(path), a); |
996 | |
|
997 | 0 | status = get_status(conn, id); |
998 | 0 | if (status != SSH2_FX_OK) |
999 | 0 | error("remote setstat \"%s\": %s", path, fx2txt(status)); |
1000 | |
|
1001 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
1002 | 0 | } |
1003 | | |
1004 | | int |
1005 | | sftp_fsetstat(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
1006 | | Attrib *a) |
1007 | 0 | { |
1008 | 0 | u_int status, id; |
1009 | |
|
1010 | 0 | debug2("Sending SSH2_FXP_FSETSTAT"); |
1011 | |
|
1012 | 0 | id = conn->msg_id++; |
1013 | 0 | send_string_attrs_request(conn, id, SSH2_FXP_FSETSTAT, handle, |
1014 | 0 | handle_len, a); |
1015 | |
|
1016 | 0 | status = get_status(conn, id); |
1017 | 0 | if (status != SSH2_FX_OK) |
1018 | 0 | error("remote fsetstat: %s", fx2txt(status)); |
1019 | |
|
1020 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
1021 | 0 | } |
1022 | | |
1023 | | /* Implements both the realpath and expand-path operations */ |
1024 | | static char * |
1025 | | sftp_realpath_expand(struct sftp_conn *conn, const char *path, int expand) |
1026 | 2.32k | { |
1027 | 2.32k | struct sshbuf *msg; |
1028 | 2.32k | u_int expected_id, count, id; |
1029 | 2.32k | char *filename, *longname; |
1030 | 2.32k | Attrib a; |
1031 | 2.32k | u_char type; |
1032 | 2.32k | int r; |
1033 | 2.32k | const char *what = "SSH2_FXP_REALPATH"; |
1034 | | |
1035 | 2.32k | if (expand) |
1036 | 0 | what = "expand-path@openssh.com"; |
1037 | 2.32k | if ((msg = sshbuf_new()) == NULL) |
1038 | 0 | fatal_f("sshbuf_new failed"); |
1039 | | |
1040 | 2.32k | expected_id = id = conn->msg_id++; |
1041 | 2.32k | if (expand) { |
1042 | 0 | debug2("Sending SSH2_FXP_EXTENDED(expand-path@openssh.com) " |
1043 | 0 | "\"%s\"", path); |
1044 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1045 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1046 | 0 | (r = sshbuf_put_cstring(msg, |
1047 | 0 | "expand-path@openssh.com")) != 0 || |
1048 | 0 | (r = sshbuf_put_cstring(msg, path)) != 0) |
1049 | 0 | fatal_fr(r, "compose %s", what); |
1050 | 0 | send_msg(conn, msg); |
1051 | 2.32k | } else { |
1052 | 2.32k | debug2("Sending SSH2_FXP_REALPATH \"%s\"", path); |
1053 | 2.32k | send_string_request(conn, id, SSH2_FXP_REALPATH, |
1054 | 2.32k | path, strlen(path)); |
1055 | 2.32k | } |
1056 | 2.32k | get_msg(conn, msg); |
1057 | 2.32k | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
1058 | 2.12k | (r = sshbuf_get_u32(msg, &id)) != 0) |
1059 | 10 | fatal_fr(r, "parse"); |
1060 | | |
1061 | 2.31k | if (id != expected_id) |
1062 | 132 | fatal("ID mismatch (%u != %u)", id, expected_id); |
1063 | | |
1064 | 2.17k | if (type == SSH2_FXP_STATUS) { |
1065 | 664 | u_int status; |
1066 | 664 | char *errmsg; |
1067 | | |
1068 | 664 | if ((r = sshbuf_get_u32(msg, &status)) != 0 || |
1069 | 662 | (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0) |
1070 | 244 | fatal_fr(r, "parse status"); |
1071 | 420 | error("%s %s: %s", expand ? "expand" : "realpath", |
1072 | 420 | path, *errmsg == '\0' ? fx2txt(status) : errmsg); |
1073 | 420 | free(errmsg); |
1074 | 420 | sshbuf_free(msg); |
1075 | 420 | return NULL; |
1076 | 1.51k | } else if (type != SSH2_FXP_NAME) |
1077 | 1.21k | fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", |
1078 | 2.17k | SSH2_FXP_NAME, type); |
1079 | | |
1080 | 300 | if ((r = sshbuf_get_u32(msg, &count)) != 0) |
1081 | 2 | fatal_fr(r, "parse count"); |
1082 | 298 | if (count != 1) |
1083 | 80 | fatal("Got multiple names (%d) from %s", count, what); |
1084 | | |
1085 | 218 | if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || |
1086 | 18 | (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || |
1087 | 14 | (r = decode_attrib(msg, &a)) != 0) |
1088 | 18 | fatal_fr(r, "parse filename/attrib"); |
1089 | | |
1090 | 200 | debug3("%s %s -> %s", what, path, filename); |
1091 | | |
1092 | 200 | free(longname); |
1093 | | |
1094 | 200 | sshbuf_free(msg); |
1095 | | |
1096 | 200 | return(filename); |
1097 | 218 | } |
1098 | | |
1099 | | char * |
1100 | | sftp_realpath(struct sftp_conn *conn, const char *path) |
1101 | 1.16k | { |
1102 | 1.16k | return sftp_realpath_expand(conn, path, 0); |
1103 | 1.16k | } |
1104 | | |
1105 | | int |
1106 | | sftp_can_expand_path(struct sftp_conn *conn) |
1107 | 1.16k | { |
1108 | 1.16k | return (conn->exts & SFTP_EXT_PATH_EXPAND) != 0; |
1109 | 1.16k | } |
1110 | | |
1111 | | char * |
1112 | | sftp_expand_path(struct sftp_conn *conn, const char *path) |
1113 | 1.16k | { |
1114 | 1.16k | if (!sftp_can_expand_path(conn)) { |
1115 | 1.16k | debug3_f("no server support, fallback to realpath"); |
1116 | 1.16k | return sftp_realpath_expand(conn, path, 0); |
1117 | 1.16k | } |
1118 | 0 | return sftp_realpath_expand(conn, path, 1); |
1119 | 1.16k | } |
1120 | | |
1121 | | int |
1122 | | sftp_copy(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
1123 | 0 | { |
1124 | 0 | Attrib junk, attr; |
1125 | 0 | struct sshbuf *msg; |
1126 | 0 | u_char *old_handle, *new_handle; |
1127 | 0 | u_int mode, status, id; |
1128 | 0 | size_t old_handle_len, new_handle_len; |
1129 | 0 | int r; |
1130 | | |
1131 | | /* Return if the extension is not supported */ |
1132 | 0 | if ((conn->exts & SFTP_EXT_COPY_DATA) == 0) { |
1133 | 0 | error("Server does not support copy-data extension"); |
1134 | 0 | return -1; |
1135 | 0 | } |
1136 | | |
1137 | | /* Make sure the file exists, and we can copy its perms */ |
1138 | 0 | if (sftp_stat(conn, oldpath, 0, &attr) != 0) |
1139 | 0 | return -1; |
1140 | | |
1141 | | /* Do not preserve set[ug]id here, as we do not preserve ownership */ |
1142 | 0 | if (attr.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { |
1143 | 0 | mode = attr.perm & 0777; |
1144 | |
|
1145 | 0 | if (!S_ISREG(attr.perm)) { |
1146 | 0 | error("Cannot copy non-regular file: %s", oldpath); |
1147 | 0 | return -1; |
1148 | 0 | } |
1149 | 0 | } else { |
1150 | | /* NB: The user's umask will apply to this */ |
1151 | 0 | mode = 0666; |
1152 | 0 | } |
1153 | | |
1154 | | /* Set up the new perms for the new file */ |
1155 | 0 | attrib_clear(&attr); |
1156 | 0 | attr.perm = mode; |
1157 | 0 | attr.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; |
1158 | |
|
1159 | 0 | if ((msg = sshbuf_new()) == NULL) |
1160 | 0 | fatal_f("sshbuf_new failed"); |
1161 | | |
1162 | 0 | attrib_clear(&junk); /* Send empty attributes */ |
1163 | | |
1164 | | /* Open the old file for reading */ |
1165 | 0 | id = conn->msg_id++; |
1166 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || |
1167 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1168 | 0 | (r = sshbuf_put_cstring(msg, oldpath)) != 0 || |
1169 | 0 | (r = sshbuf_put_u32(msg, SSH2_FXF_READ)) != 0 || |
1170 | 0 | (r = encode_attrib(msg, &junk)) != 0) |
1171 | 0 | fatal_fr(r, "buffer error"); |
1172 | 0 | send_msg(conn, msg); |
1173 | 0 | debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, oldpath); |
1174 | |
|
1175 | 0 | sshbuf_reset(msg); |
1176 | |
|
1177 | 0 | old_handle = get_handle(conn, id, &old_handle_len, |
1178 | 0 | "remote open(\"%s\")", oldpath); |
1179 | 0 | if (old_handle == NULL) { |
1180 | 0 | sshbuf_free(msg); |
1181 | 0 | return -1; |
1182 | 0 | } |
1183 | | |
1184 | | /* Open the new file for writing */ |
1185 | 0 | id = conn->msg_id++; |
1186 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || |
1187 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1188 | 0 | (r = sshbuf_put_cstring(msg, newpath)) != 0 || |
1189 | 0 | (r = sshbuf_put_u32(msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT| |
1190 | 0 | SSH2_FXF_TRUNC)) != 0 || |
1191 | 0 | (r = encode_attrib(msg, &attr)) != 0) |
1192 | 0 | fatal_fr(r, "buffer error"); |
1193 | 0 | send_msg(conn, msg); |
1194 | 0 | debug3("Sent message SSH2_FXP_OPEN I:%u P:%s", id, newpath); |
1195 | |
|
1196 | 0 | sshbuf_reset(msg); |
1197 | |
|
1198 | 0 | new_handle = get_handle(conn, id, &new_handle_len, |
1199 | 0 | "remote open(\"%s\")", newpath); |
1200 | 0 | if (new_handle == NULL) { |
1201 | 0 | sshbuf_free(msg); |
1202 | 0 | free(old_handle); |
1203 | 0 | return -1; |
1204 | 0 | } |
1205 | | |
1206 | | /* Copy the file data */ |
1207 | 0 | id = conn->msg_id++; |
1208 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1209 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1210 | 0 | (r = sshbuf_put_cstring(msg, "copy-data")) != 0 || |
1211 | 0 | (r = sshbuf_put_string(msg, old_handle, old_handle_len)) != 0 || |
1212 | 0 | (r = sshbuf_put_u64(msg, 0)) != 0 || |
1213 | 0 | (r = sshbuf_put_u64(msg, 0)) != 0 || |
1214 | 0 | (r = sshbuf_put_string(msg, new_handle, new_handle_len)) != 0 || |
1215 | 0 | (r = sshbuf_put_u64(msg, 0)) != 0) |
1216 | 0 | fatal_fr(r, "buffer error"); |
1217 | 0 | send_msg(conn, msg); |
1218 | 0 | debug3("Sent message copy-data \"%s\" 0 0 -> \"%s\" 0", |
1219 | 0 | oldpath, newpath); |
1220 | |
|
1221 | 0 | status = get_status(conn, id); |
1222 | 0 | if (status != SSH2_FX_OK) |
1223 | 0 | error("Couldn't copy file \"%s\" to \"%s\": %s", oldpath, |
1224 | 0 | newpath, fx2txt(status)); |
1225 | | |
1226 | | /* Clean up everything */ |
1227 | 0 | sshbuf_free(msg); |
1228 | 0 | sftp_close(conn, old_handle, old_handle_len); |
1229 | 0 | sftp_close(conn, new_handle, new_handle_len); |
1230 | 0 | free(old_handle); |
1231 | 0 | free(new_handle); |
1232 | |
|
1233 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
1234 | 0 | } |
1235 | | |
1236 | | int |
1237 | | sftp_rename(struct sftp_conn *conn, const char *oldpath, const char *newpath, |
1238 | | int force_legacy) |
1239 | 1.16k | { |
1240 | 1.16k | struct sshbuf *msg; |
1241 | 1.16k | u_int status, id; |
1242 | 1.16k | int r, use_ext = (conn->exts & SFTP_EXT_POSIX_RENAME) && !force_legacy; |
1243 | | |
1244 | 1.16k | if ((msg = sshbuf_new()) == NULL) |
1245 | 0 | fatal_f("sshbuf_new failed"); |
1246 | | |
1247 | | /* Send rename request */ |
1248 | 1.16k | id = conn->msg_id++; |
1249 | 1.16k | if (use_ext) { |
1250 | 0 | debug2("Sending SSH2_FXP_EXTENDED(posix-rename@openssh.com) " |
1251 | 0 | "\"%s\" to \"%s\"", oldpath, newpath); |
1252 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1253 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1254 | 0 | (r = sshbuf_put_cstring(msg, |
1255 | 0 | "posix-rename@openssh.com")) != 0) |
1256 | 0 | fatal_fr(r, "compose posix-rename"); |
1257 | 1.16k | } else { |
1258 | 1.16k | debug2("Sending SSH2_FXP_RENAME \"%s\" to \"%s\"", |
1259 | 1.16k | oldpath, newpath); |
1260 | 1.16k | if ((r = sshbuf_put_u8(msg, SSH2_FXP_RENAME)) != 0 || |
1261 | 1.16k | (r = sshbuf_put_u32(msg, id)) != 0) |
1262 | 0 | fatal_fr(r, "compose rename"); |
1263 | 1.16k | } |
1264 | 1.16k | if ((r = sshbuf_put_cstring(msg, oldpath)) != 0 || |
1265 | 1.16k | (r = sshbuf_put_cstring(msg, newpath)) != 0) |
1266 | 0 | fatal_fr(r, "compose paths"); |
1267 | 1.16k | send_msg(conn, msg); |
1268 | 1.16k | debug3("Sent message %s \"%s\" -> \"%s\"", |
1269 | 1.16k | use_ext ? "posix-rename@openssh.com" : |
1270 | 1.16k | "SSH2_FXP_RENAME", oldpath, newpath); |
1271 | 1.16k | sshbuf_free(msg); |
1272 | | |
1273 | 1.16k | status = get_status(conn, id); |
1274 | 1.16k | if (status != SSH2_FX_OK) |
1275 | 312 | error("remote rename \"%s\" to \"%s\": %s", oldpath, |
1276 | 1.16k | newpath, fx2txt(status)); |
1277 | | |
1278 | 1.16k | return status == SSH2_FX_OK ? 0 : -1; |
1279 | 1.16k | } |
1280 | | |
1281 | | int |
1282 | | sftp_hardlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
1283 | 0 | { |
1284 | 0 | struct sshbuf *msg; |
1285 | 0 | u_int status, id; |
1286 | 0 | int r; |
1287 | |
|
1288 | 0 | if ((conn->exts & SFTP_EXT_HARDLINK) == 0) { |
1289 | 0 | error("Server does not support hardlink@openssh.com extension"); |
1290 | 0 | return -1; |
1291 | 0 | } |
1292 | 0 | debug2("Sending SSH2_FXP_EXTENDED(hardlink@openssh.com) " |
1293 | 0 | "\"%s\" to \"%s\"", oldpath, newpath); |
1294 | |
|
1295 | 0 | if ((msg = sshbuf_new()) == NULL) |
1296 | 0 | fatal_f("sshbuf_new failed"); |
1297 | | |
1298 | | /* Send link request */ |
1299 | 0 | id = conn->msg_id++; |
1300 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1301 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1302 | 0 | (r = sshbuf_put_cstring(msg, "hardlink@openssh.com")) != 0 || |
1303 | 0 | (r = sshbuf_put_cstring(msg, oldpath)) != 0 || |
1304 | 0 | (r = sshbuf_put_cstring(msg, newpath)) != 0) |
1305 | 0 | fatal_fr(r, "compose"); |
1306 | 0 | send_msg(conn, msg); |
1307 | 0 | debug3("Sent message hardlink@openssh.com \"%s\" -> \"%s\"", |
1308 | 0 | oldpath, newpath); |
1309 | 0 | sshbuf_free(msg); |
1310 | |
|
1311 | 0 | status = get_status(conn, id); |
1312 | 0 | if (status != SSH2_FX_OK) |
1313 | 0 | error("remote link \"%s\" to \"%s\": %s", oldpath, |
1314 | 0 | newpath, fx2txt(status)); |
1315 | |
|
1316 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
1317 | 0 | } |
1318 | | |
1319 | | int |
1320 | | sftp_symlink(struct sftp_conn *conn, const char *oldpath, const char *newpath) |
1321 | 1.16k | { |
1322 | 1.16k | struct sshbuf *msg; |
1323 | 1.16k | u_int status, id; |
1324 | 1.16k | int r; |
1325 | | |
1326 | 1.16k | if (conn->version < 3) { |
1327 | 0 | error("This server does not support the symlink operation"); |
1328 | 0 | return(SSH2_FX_OP_UNSUPPORTED); |
1329 | 0 | } |
1330 | 1.16k | debug2("Sending SSH2_FXP_SYMLINK \"%s\" to \"%s\"", oldpath, newpath); |
1331 | | |
1332 | 1.16k | if ((msg = sshbuf_new()) == NULL) |
1333 | 0 | fatal_f("sshbuf_new failed"); |
1334 | | |
1335 | | /* Send symlink request */ |
1336 | 1.16k | id = conn->msg_id++; |
1337 | 1.16k | if ((r = sshbuf_put_u8(msg, SSH2_FXP_SYMLINK)) != 0 || |
1338 | 1.16k | (r = sshbuf_put_u32(msg, id)) != 0 || |
1339 | 1.16k | (r = sshbuf_put_cstring(msg, oldpath)) != 0 || |
1340 | 1.16k | (r = sshbuf_put_cstring(msg, newpath)) != 0) |
1341 | 0 | fatal_fr(r, "compose"); |
1342 | 1.16k | send_msg(conn, msg); |
1343 | 1.16k | debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath, |
1344 | 1.16k | newpath); |
1345 | 1.16k | sshbuf_free(msg); |
1346 | | |
1347 | 1.16k | status = get_status(conn, id); |
1348 | 1.16k | if (status != SSH2_FX_OK) |
1349 | 312 | error("remote symlink file \"%s\" to \"%s\": %s", oldpath, |
1350 | 1.16k | newpath, fx2txt(status)); |
1351 | | |
1352 | 1.16k | return status == SSH2_FX_OK ? 0 : -1; |
1353 | 1.16k | } |
1354 | | |
1355 | | int |
1356 | | sftp_fsync(struct sftp_conn *conn, u_char *handle, u_int handle_len) |
1357 | 0 | { |
1358 | 0 | struct sshbuf *msg; |
1359 | 0 | u_int status, id; |
1360 | 0 | int r; |
1361 | | |
1362 | | /* Silently return if the extension is not supported */ |
1363 | 0 | if ((conn->exts & SFTP_EXT_FSYNC) == 0) |
1364 | 0 | return -1; |
1365 | 0 | debug2("Sending SSH2_FXP_EXTENDED(fsync@openssh.com)"); |
1366 | | |
1367 | | /* Send fsync request */ |
1368 | 0 | if ((msg = sshbuf_new()) == NULL) |
1369 | 0 | fatal_f("sshbuf_new failed"); |
1370 | 0 | id = conn->msg_id++; |
1371 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1372 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1373 | 0 | (r = sshbuf_put_cstring(msg, "fsync@openssh.com")) != 0 || |
1374 | 0 | (r = sshbuf_put_string(msg, handle, handle_len)) != 0) |
1375 | 0 | fatal_fr(r, "compose"); |
1376 | 0 | send_msg(conn, msg); |
1377 | 0 | debug3("Sent message fsync@openssh.com I:%u", id); |
1378 | 0 | sshbuf_free(msg); |
1379 | |
|
1380 | 0 | status = get_status(conn, id); |
1381 | 0 | if (status != SSH2_FX_OK) |
1382 | 0 | error("remote fsync: %s", fx2txt(status)); |
1383 | |
|
1384 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
1385 | 0 | } |
1386 | | |
1387 | | #ifdef notyet |
1388 | | char * |
1389 | | sftp_readlink(struct sftp_conn *conn, const char *path) |
1390 | | { |
1391 | | struct sshbuf *msg; |
1392 | | u_int expected_id, count, id; |
1393 | | char *filename, *longname; |
1394 | | Attrib a; |
1395 | | u_char type; |
1396 | | int r; |
1397 | | |
1398 | | debug2("Sending SSH2_FXP_READLINK \"%s\"", path); |
1399 | | |
1400 | | expected_id = id = conn->msg_id++; |
1401 | | send_string_request(conn, id, SSH2_FXP_READLINK, path, strlen(path)); |
1402 | | |
1403 | | if ((msg = sshbuf_new()) == NULL) |
1404 | | fatal_f("sshbuf_new failed"); |
1405 | | |
1406 | | get_msg(conn, msg); |
1407 | | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
1408 | | (r = sshbuf_get_u32(msg, &id)) != 0) |
1409 | | fatal_fr(r, "parse"); |
1410 | | |
1411 | | if (id != expected_id) |
1412 | | fatal("ID mismatch (%u != %u)", id, expected_id); |
1413 | | |
1414 | | if (type == SSH2_FXP_STATUS) { |
1415 | | u_int status; |
1416 | | |
1417 | | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
1418 | | fatal_fr(r, "parse status"); |
1419 | | error("Couldn't readlink: %s", fx2txt(status)); |
1420 | | sshbuf_free(msg); |
1421 | | return(NULL); |
1422 | | } else if (type != SSH2_FXP_NAME) |
1423 | | fatal("Expected SSH2_FXP_NAME(%u) packet, got %u", |
1424 | | SSH2_FXP_NAME, type); |
1425 | | |
1426 | | if ((r = sshbuf_get_u32(msg, &count)) != 0) |
1427 | | fatal_fr(r, "parse count"); |
1428 | | if (count != 1) |
1429 | | fatal("Got multiple names (%d) from SSH_FXP_READLINK", count); |
1430 | | |
1431 | | if ((r = sshbuf_get_cstring(msg, &filename, NULL)) != 0 || |
1432 | | (r = sshbuf_get_cstring(msg, &longname, NULL)) != 0 || |
1433 | | (r = decode_attrib(msg, &a)) != 0) |
1434 | | fatal_fr(r, "parse filenames/attrib"); |
1435 | | |
1436 | | debug3("SSH_FXP_READLINK %s -> %s", path, filename); |
1437 | | |
1438 | | free(longname); |
1439 | | |
1440 | | sshbuf_free(msg); |
1441 | | |
1442 | | return filename; |
1443 | | } |
1444 | | #endif |
1445 | | |
1446 | | int |
1447 | | sftp_statvfs(struct sftp_conn *conn, const char *path, struct sftp_statvfs *st, |
1448 | | int quiet) |
1449 | 1.16k | { |
1450 | 1.16k | struct sshbuf *msg; |
1451 | 1.16k | u_int id; |
1452 | 1.16k | int r; |
1453 | | |
1454 | 1.16k | if ((conn->exts & SFTP_EXT_STATVFS) == 0) { |
1455 | 1.16k | error("Server does not support statvfs@openssh.com extension"); |
1456 | 1.16k | return -1; |
1457 | 1.16k | } |
1458 | | |
1459 | 0 | debug2("Sending SSH2_FXP_EXTENDED(statvfs@openssh.com) \"%s\"", path); |
1460 | |
|
1461 | 0 | id = conn->msg_id++; |
1462 | |
|
1463 | 0 | if ((msg = sshbuf_new()) == NULL) |
1464 | 0 | fatal_f("sshbuf_new failed"); |
1465 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1466 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1467 | 0 | (r = sshbuf_put_cstring(msg, "statvfs@openssh.com")) != 0 || |
1468 | 0 | (r = sshbuf_put_cstring(msg, path)) != 0) |
1469 | 0 | fatal_fr(r, "compose"); |
1470 | 0 | send_msg(conn, msg); |
1471 | 0 | sshbuf_free(msg); |
1472 | |
|
1473 | 0 | return get_decode_statvfs(conn, st, id, quiet); |
1474 | 0 | } |
1475 | | |
1476 | | #ifdef notyet |
1477 | | int |
1478 | | sftp_fstatvfs(struct sftp_conn *conn, const u_char *handle, u_int handle_len, |
1479 | | struct sftp_statvfs *st, int quiet) |
1480 | | { |
1481 | | struct sshbuf *msg; |
1482 | | u_int id; |
1483 | | |
1484 | | if ((conn->exts & SFTP_EXT_FSTATVFS) == 0) { |
1485 | | error("Server does not support fstatvfs@openssh.com extension"); |
1486 | | return -1; |
1487 | | } |
1488 | | |
1489 | | debug2("Sending SSH2_FXP_EXTENDED(fstatvfs@openssh.com)"); |
1490 | | |
1491 | | id = conn->msg_id++; |
1492 | | |
1493 | | if ((msg = sshbuf_new()) == NULL) |
1494 | | fatal_f("sshbuf_new failed"); |
1495 | | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1496 | | (r = sshbuf_put_u32(msg, id)) != 0 || |
1497 | | (r = sshbuf_put_cstring(msg, "fstatvfs@openssh.com")) != 0 || |
1498 | | (r = sshbuf_put_string(msg, handle, handle_len)) != 0) |
1499 | | fatal_fr(r, "compose"); |
1500 | | send_msg(conn, msg); |
1501 | | sshbuf_free(msg); |
1502 | | |
1503 | | return get_decode_statvfs(conn, st, id, quiet); |
1504 | | } |
1505 | | #endif |
1506 | | |
1507 | | int |
1508 | | sftp_lsetstat(struct sftp_conn *conn, const char *path, Attrib *a) |
1509 | 0 | { |
1510 | 0 | struct sshbuf *msg; |
1511 | 0 | u_int status, id; |
1512 | 0 | int r; |
1513 | |
|
1514 | 0 | if ((conn->exts & SFTP_EXT_LSETSTAT) == 0) { |
1515 | 0 | error("Server does not support lsetstat@openssh.com extension"); |
1516 | 0 | return -1; |
1517 | 0 | } |
1518 | | |
1519 | 0 | debug2("Sending SSH2_FXP_EXTENDED(lsetstat@openssh.com) \"%s\"", path); |
1520 | |
|
1521 | 0 | id = conn->msg_id++; |
1522 | 0 | if ((msg = sshbuf_new()) == NULL) |
1523 | 0 | fatal_f("sshbuf_new failed"); |
1524 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
1525 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1526 | 0 | (r = sshbuf_put_cstring(msg, "lsetstat@openssh.com")) != 0 || |
1527 | 0 | (r = sshbuf_put_cstring(msg, path)) != 0 || |
1528 | 0 | (r = encode_attrib(msg, a)) != 0) |
1529 | 0 | fatal_fr(r, "compose"); |
1530 | 0 | send_msg(conn, msg); |
1531 | 0 | sshbuf_free(msg); |
1532 | |
|
1533 | 0 | status = get_status(conn, id); |
1534 | 0 | if (status != SSH2_FX_OK) |
1535 | 0 | error("remote lsetstat \"%s\": %s", path, fx2txt(status)); |
1536 | |
|
1537 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
1538 | 0 | } |
1539 | | |
1540 | | static void |
1541 | | send_read_request(struct sftp_conn *conn, u_int id, uint64_t offset, |
1542 | | u_int len, const u_char *handle, u_int handle_len) |
1543 | 0 | { |
1544 | 0 | struct sshbuf *msg; |
1545 | 0 | int r; |
1546 | |
|
1547 | 0 | if ((msg = sshbuf_new()) == NULL) |
1548 | 0 | fatal_f("sshbuf_new failed"); |
1549 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_READ)) != 0 || |
1550 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1551 | 0 | (r = sshbuf_put_string(msg, handle, handle_len)) != 0 || |
1552 | 0 | (r = sshbuf_put_u64(msg, offset)) != 0 || |
1553 | 0 | (r = sshbuf_put_u32(msg, len)) != 0) |
1554 | 0 | fatal_fr(r, "compose"); |
1555 | 0 | send_msg(conn, msg); |
1556 | 0 | sshbuf_free(msg); |
1557 | 0 | } |
1558 | | |
1559 | | static int |
1560 | | send_open(struct sftp_conn *conn, const char *path, const char *tag, |
1561 | | u_int openmode, Attrib *a, u_char **handlep, size_t *handle_lenp) |
1562 | 0 | { |
1563 | 0 | Attrib junk; |
1564 | 0 | u_char *handle; |
1565 | 0 | size_t handle_len; |
1566 | 0 | struct sshbuf *msg; |
1567 | 0 | int r; |
1568 | 0 | u_int id; |
1569 | |
|
1570 | 0 | debug2("Sending SSH2_FXP_OPEN \"%s\"", path); |
1571 | |
|
1572 | 0 | *handlep = NULL; |
1573 | 0 | *handle_lenp = 0; |
1574 | |
|
1575 | 0 | if (a == NULL) { |
1576 | 0 | attrib_clear(&junk); /* Send empty attributes */ |
1577 | 0 | a = &junk; |
1578 | 0 | } |
1579 | | /* Send open request */ |
1580 | 0 | if ((msg = sshbuf_new()) == NULL) |
1581 | 0 | fatal_f("sshbuf_new failed"); |
1582 | 0 | id = conn->msg_id++; |
1583 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_OPEN)) != 0 || |
1584 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
1585 | 0 | (r = sshbuf_put_cstring(msg, path)) != 0 || |
1586 | 0 | (r = sshbuf_put_u32(msg, openmode)) != 0 || |
1587 | 0 | (r = encode_attrib(msg, a)) != 0) |
1588 | 0 | fatal_fr(r, "compose %s open", tag); |
1589 | 0 | send_msg(conn, msg); |
1590 | 0 | sshbuf_free(msg); |
1591 | 0 | debug3("Sent %s message SSH2_FXP_OPEN I:%u P:%s M:0x%04x", |
1592 | 0 | tag, id, path, openmode); |
1593 | 0 | if ((handle = get_handle(conn, id, &handle_len, |
1594 | 0 | "%s open \"%s\"", tag, path)) == NULL) |
1595 | 0 | return -1; |
1596 | | /* success */ |
1597 | 0 | *handlep = handle; |
1598 | 0 | *handle_lenp = handle_len; |
1599 | 0 | return 0; |
1600 | 0 | } |
1601 | | |
1602 | | static const char * |
1603 | | progress_meter_path(const char *path) |
1604 | 0 | { |
1605 | 0 | const char *progresspath; |
1606 | |
|
1607 | 0 | if ((progresspath = strrchr(path, '/')) == NULL) |
1608 | 0 | return path; |
1609 | 0 | progresspath++; |
1610 | 0 | if (*progresspath == '\0') |
1611 | 0 | return path; |
1612 | 0 | return progresspath; |
1613 | 0 | } |
1614 | | |
1615 | | int |
1616 | | sftp_download(struct sftp_conn *conn, const char *remote_path, |
1617 | | const char *local_path, Attrib *a, int preserve_flag, int resume_flag, |
1618 | | int fsync_flag, int inplace_flag) |
1619 | 0 | { |
1620 | 0 | struct sshbuf *msg; |
1621 | 0 | u_char *handle; |
1622 | 0 | int local_fd = -1, write_error, seen_zerolen = 0; |
1623 | 0 | int read_error, write_errno, lmodified = 0, reordered = 0, r; |
1624 | 0 | uint64_t offset = 0, size, highwater = 0, maxack = 0; |
1625 | 0 | u_int mode, id, buflen, num_req, max_req, status = SSH2_FX_OK; |
1626 | 0 | off_t progress_counter; |
1627 | 0 | size_t handle_len; |
1628 | 0 | struct stat st; |
1629 | 0 | struct requests requests; |
1630 | 0 | struct request *req; |
1631 | 0 | u_char type; |
1632 | 0 | Attrib attr; |
1633 | |
|
1634 | 0 | debug2_f("download remote \"%s\" to local \"%s\"", |
1635 | 0 | remote_path, local_path); |
1636 | |
|
1637 | 0 | TAILQ_INIT(&requests); |
1638 | |
|
1639 | 0 | if (a == NULL) { |
1640 | 0 | if (sftp_stat(conn, remote_path, 0, &attr) != 0) |
1641 | 0 | return -1; |
1642 | 0 | a = &attr; |
1643 | 0 | } |
1644 | | |
1645 | | /* Do not preserve set[ug]id here, as we do not preserve ownership */ |
1646 | 0 | if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) |
1647 | 0 | mode = a->perm & 0777; |
1648 | 0 | else |
1649 | 0 | mode = 0666; |
1650 | |
|
1651 | 0 | if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && |
1652 | 0 | (!S_ISREG(a->perm))) { |
1653 | 0 | error("download %s: not a regular file", remote_path); |
1654 | 0 | return(-1); |
1655 | 0 | } |
1656 | | |
1657 | 0 | if (a->flags & SSH2_FILEXFER_ATTR_SIZE) |
1658 | 0 | size = a->size; |
1659 | 0 | else |
1660 | 0 | size = 0; |
1661 | |
|
1662 | 0 | buflen = conn->download_buflen; |
1663 | | |
1664 | | /* Send open request */ |
1665 | 0 | if (send_open(conn, remote_path, "remote", SSH2_FXF_READ, NULL, |
1666 | 0 | &handle, &handle_len) != 0) |
1667 | 0 | return -1; |
1668 | | |
1669 | 0 | local_fd = open(local_path, O_WRONLY | O_CREAT | |
1670 | 0 | ((resume_flag || inplace_flag) ? 0 : O_TRUNC), mode | S_IWUSR); |
1671 | 0 | if (local_fd == -1) { |
1672 | 0 | error("open local \"%s\": %s", local_path, strerror(errno)); |
1673 | 0 | goto fail; |
1674 | 0 | } |
1675 | 0 | if (resume_flag) { |
1676 | 0 | if (fstat(local_fd, &st) == -1) { |
1677 | 0 | error("stat local \"%s\": %s", |
1678 | 0 | local_path, strerror(errno)); |
1679 | 0 | goto fail; |
1680 | 0 | } |
1681 | 0 | if (st.st_size < 0) { |
1682 | 0 | error("\"%s\" has negative size", local_path); |
1683 | 0 | goto fail; |
1684 | 0 | } |
1685 | 0 | if ((uint64_t)st.st_size > size) { |
1686 | 0 | error("Unable to resume download of \"%s\": " |
1687 | 0 | "local file is larger than remote", local_path); |
1688 | 0 | fail: |
1689 | 0 | sftp_close(conn, handle, handle_len); |
1690 | 0 | free(handle); |
1691 | 0 | if (local_fd != -1) |
1692 | 0 | close(local_fd); |
1693 | 0 | return -1; |
1694 | 0 | } |
1695 | 0 | offset = highwater = maxack = st.st_size; |
1696 | 0 | } |
1697 | | |
1698 | | /* Read from remote and write to local */ |
1699 | 0 | write_error = read_error = write_errno = num_req = 0; |
1700 | 0 | max_req = 1; |
1701 | 0 | progress_counter = offset; |
1702 | |
|
1703 | 0 | if (showprogress && size != 0) { |
1704 | 0 | start_progress_meter(progress_meter_path(remote_path), |
1705 | 0 | size, &progress_counter); |
1706 | 0 | } |
1707 | |
|
1708 | 0 | if ((msg = sshbuf_new()) == NULL) |
1709 | 0 | fatal_f("sshbuf_new failed"); |
1710 | | |
1711 | 0 | while (num_req > 0 || max_req > 0) { |
1712 | 0 | u_char *data; |
1713 | 0 | size_t len; |
1714 | | |
1715 | | /* |
1716 | | * Simulate EOF on interrupt: stop sending new requests and |
1717 | | * allow outstanding requests to drain gracefully |
1718 | | */ |
1719 | 0 | if (interrupted) { |
1720 | 0 | if (num_req == 0) /* If we haven't started yet... */ |
1721 | 0 | break; |
1722 | 0 | max_req = 0; |
1723 | 0 | } |
1724 | | |
1725 | | /* Send some more requests */ |
1726 | 0 | while (num_req < max_req) { |
1727 | 0 | debug3("Request range %llu -> %llu (%d/%d)", |
1728 | 0 | (unsigned long long)offset, |
1729 | 0 | (unsigned long long)offset + buflen - 1, |
1730 | 0 | num_req, max_req); |
1731 | 0 | req = request_enqueue(&requests, conn->msg_id++, |
1732 | 0 | buflen, offset); |
1733 | 0 | offset += buflen; |
1734 | 0 | num_req++; |
1735 | 0 | send_read_request(conn, req->id, req->offset, |
1736 | 0 | req->len, handle, handle_len); |
1737 | 0 | } |
1738 | |
|
1739 | 0 | sshbuf_reset(msg); |
1740 | 0 | get_msg(conn, msg); |
1741 | 0 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
1742 | 0 | (r = sshbuf_get_u32(msg, &id)) != 0) |
1743 | 0 | fatal_fr(r, "parse"); |
1744 | 0 | debug3("Received reply T:%u I:%u R:%d", type, id, max_req); |
1745 | | |
1746 | | /* Find the request in our queue */ |
1747 | 0 | if ((req = request_find(&requests, id)) == NULL) |
1748 | 0 | fatal("Unexpected reply %u", id); |
1749 | | |
1750 | 0 | switch (type) { |
1751 | 0 | case SSH2_FXP_STATUS: |
1752 | 0 | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
1753 | 0 | fatal_fr(r, "parse status"); |
1754 | 0 | if (status != SSH2_FX_EOF) |
1755 | 0 | read_error = 1; |
1756 | 0 | max_req = 0; |
1757 | 0 | TAILQ_REMOVE(&requests, req, tq); |
1758 | 0 | free(req); |
1759 | 0 | num_req--; |
1760 | 0 | break; |
1761 | 0 | case SSH2_FXP_DATA: |
1762 | 0 | if ((r = sshbuf_get_string(msg, &data, &len)) != 0) |
1763 | 0 | fatal_fr(r, "parse data"); |
1764 | 0 | debug3("Received data %llu -> %llu", |
1765 | 0 | (unsigned long long)req->offset, |
1766 | 0 | (unsigned long long)req->offset + len - 1); |
1767 | 0 | if (len > req->len) |
1768 | 0 | fatal("Received more data than asked for " |
1769 | 0 | "%zu > %zu", len, req->len); |
1770 | 0 | if (len == 0) { |
1771 | 0 | if (seen_zerolen) |
1772 | 0 | fatal_f("server sent zero data length"); |
1773 | 0 | seen_zerolen = 1; |
1774 | 0 | } |
1775 | 0 | lmodified = 1; |
1776 | 0 | if ((lseek(local_fd, req->offset, SEEK_SET) == -1 || |
1777 | 0 | atomicio(vwrite, local_fd, data, len) != len) && |
1778 | 0 | !write_error) { |
1779 | 0 | write_errno = errno; |
1780 | 0 | write_error = 1; |
1781 | 0 | max_req = 0; |
1782 | 0 | } else { |
1783 | | /* |
1784 | | * Track both the highest offset acknowledged |
1785 | | * and the highest *contiguous* offset |
1786 | | * acknowledged. |
1787 | | * We'll need the latter for ftruncate()ing |
1788 | | * interrupted transfers. |
1789 | | */ |
1790 | 0 | if (maxack < req->offset + len) |
1791 | 0 | maxack = req->offset + len; |
1792 | 0 | if (!reordered && req->offset <= highwater) |
1793 | 0 | highwater = maxack; |
1794 | 0 | else if (!reordered && req->offset > highwater) |
1795 | 0 | reordered = 1; |
1796 | 0 | } |
1797 | 0 | progress_counter += len; |
1798 | 0 | free(data); |
1799 | |
|
1800 | 0 | if (len == req->len) { |
1801 | 0 | TAILQ_REMOVE(&requests, req, tq); |
1802 | 0 | free(req); |
1803 | 0 | num_req--; |
1804 | 0 | } else { |
1805 | | /* Resend the request for the missing data */ |
1806 | 0 | debug3("Short data block, re-requesting " |
1807 | 0 | "%llu -> %llu (%2d)", |
1808 | 0 | (unsigned long long)req->offset + len, |
1809 | 0 | (unsigned long long)req->offset + |
1810 | 0 | req->len - 1, num_req); |
1811 | 0 | req->id = conn->msg_id++; |
1812 | 0 | req->len -= len; |
1813 | 0 | req->offset += len; |
1814 | 0 | send_read_request(conn, req->id, |
1815 | 0 | req->offset, req->len, handle, handle_len); |
1816 | | /* Reduce the request size */ |
1817 | 0 | if (len < buflen) |
1818 | 0 | buflen = MAXIMUM(MIN_READ_SIZE, len); |
1819 | 0 | } |
1820 | 0 | if (max_req > 0) { /* max_req = 0 iff EOF received */ |
1821 | 0 | if (size > 0 && offset > size) { |
1822 | | /* Only one request at a time |
1823 | | * after the expected EOF */ |
1824 | 0 | debug3("Finish at %llu (%2d)", |
1825 | 0 | (unsigned long long)offset, |
1826 | 0 | num_req); |
1827 | 0 | max_req = 1; |
1828 | 0 | } else if (max_req < conn->num_requests) { |
1829 | 0 | ++max_req; |
1830 | 0 | } |
1831 | 0 | } |
1832 | 0 | break; |
1833 | 0 | default: |
1834 | 0 | fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", |
1835 | 0 | SSH2_FXP_DATA, type); |
1836 | 0 | } |
1837 | 0 | } |
1838 | | |
1839 | 0 | if (showprogress && size) |
1840 | 0 | stop_progress_meter(); |
1841 | | |
1842 | | /* Sanity check */ |
1843 | 0 | if (TAILQ_FIRST(&requests) != NULL) |
1844 | 0 | fatal("Transfer complete, but requests still in queue"); |
1845 | | |
1846 | 0 | if (!read_error && !write_error && !interrupted) { |
1847 | | /* we got everything */ |
1848 | 0 | highwater = maxack; |
1849 | 0 | } |
1850 | | |
1851 | | /* |
1852 | | * Truncate at highest contiguous point to avoid holes on interrupt, |
1853 | | * or unconditionally if writing in place. |
1854 | | */ |
1855 | 0 | if (inplace_flag || read_error || write_error || interrupted) { |
1856 | 0 | if (reordered && resume_flag && |
1857 | 0 | (read_error || write_error || interrupted)) { |
1858 | 0 | error("Unable to resume download of \"%s\": " |
1859 | 0 | "server reordered requests", local_path); |
1860 | 0 | } |
1861 | 0 | debug("truncating at %llu", (unsigned long long)highwater); |
1862 | 0 | if (ftruncate(local_fd, highwater) == -1) |
1863 | 0 | error("local ftruncate \"%s\": %s", local_path, |
1864 | 0 | strerror(errno)); |
1865 | 0 | } |
1866 | 0 | if (read_error) { |
1867 | 0 | error("read remote \"%s\" : %s", remote_path, fx2txt(status)); |
1868 | 0 | status = -1; |
1869 | 0 | sftp_close(conn, handle, handle_len); |
1870 | 0 | } else if (write_error) { |
1871 | 0 | error("write local \"%s\": %s", local_path, |
1872 | 0 | strerror(write_errno)); |
1873 | 0 | status = SSH2_FX_FAILURE; |
1874 | 0 | sftp_close(conn, handle, handle_len); |
1875 | 0 | } else { |
1876 | 0 | if (sftp_close(conn, handle, handle_len) != 0 || interrupted) |
1877 | 0 | status = SSH2_FX_FAILURE; |
1878 | 0 | else |
1879 | 0 | status = SSH2_FX_OK; |
1880 | | /* Override umask and utimes if asked */ |
1881 | 0 | #ifdef HAVE_FCHMOD |
1882 | 0 | if (preserve_flag && fchmod(local_fd, mode) == -1) |
1883 | | #else |
1884 | | if (preserve_flag && chmod(local_path, mode) == -1) |
1885 | | #endif /* HAVE_FCHMOD */ |
1886 | 0 | error("local chmod \"%s\": %s", local_path, |
1887 | 0 | strerror(errno)); |
1888 | 0 | if (preserve_flag && |
1889 | 0 | (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) { |
1890 | 0 | struct timeval tv[2]; |
1891 | 0 | tv[0].tv_sec = a->atime; |
1892 | 0 | tv[1].tv_sec = a->mtime; |
1893 | 0 | tv[0].tv_usec = tv[1].tv_usec = 0; |
1894 | 0 | if (utimes(local_path, tv) == -1) |
1895 | 0 | error("local set times \"%s\": %s", |
1896 | 0 | local_path, strerror(errno)); |
1897 | 0 | } |
1898 | 0 | if (resume_flag && !lmodified) |
1899 | 0 | logit("File \"%s\" was not modified", local_path); |
1900 | 0 | else if (fsync_flag) { |
1901 | 0 | debug("syncing \"%s\"", local_path); |
1902 | 0 | if (fsync(local_fd) == -1) |
1903 | 0 | error("local sync \"%s\": %s", |
1904 | 0 | local_path, strerror(errno)); |
1905 | 0 | } |
1906 | 0 | } |
1907 | 0 | close(local_fd); |
1908 | 0 | sshbuf_free(msg); |
1909 | 0 | free(handle); |
1910 | |
|
1911 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
1912 | 0 | } |
1913 | | |
1914 | | static int |
1915 | | download_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, |
1916 | | int depth, Attrib *dirattrib, int preserve_flag, int print_flag, |
1917 | | int resume_flag, int fsync_flag, int follow_link_flag, int inplace_flag) |
1918 | 0 | { |
1919 | 0 | int i, ret = 0; |
1920 | 0 | SFTP_DIRENT **dir_entries; |
1921 | 0 | char *filename, *new_src = NULL, *new_dst = NULL; |
1922 | 0 | mode_t mode = 0777, tmpmode = mode; |
1923 | 0 | Attrib *a, ldirattrib, lsym; |
1924 | |
|
1925 | 0 | if (depth >= MAX_DIR_DEPTH) { |
1926 | 0 | error("Maximum directory depth exceeded: %d levels", depth); |
1927 | 0 | return -1; |
1928 | 0 | } |
1929 | | |
1930 | 0 | debug2_f("download dir remote \"%s\" to local \"%s\"", src, dst); |
1931 | |
|
1932 | 0 | if (dirattrib == NULL) { |
1933 | 0 | if (sftp_stat(conn, src, 1, &ldirattrib) != 0) { |
1934 | 0 | error("stat remote \"%s\" directory failed", src); |
1935 | 0 | return -1; |
1936 | 0 | } |
1937 | 0 | dirattrib = &ldirattrib; |
1938 | 0 | } |
1939 | 0 | if (!S_ISDIR(dirattrib->perm)) { |
1940 | 0 | error("\"%s\" is not a directory", src); |
1941 | 0 | return -1; |
1942 | 0 | } |
1943 | 0 | if (print_flag && print_flag != SFTP_PROGRESS_ONLY) |
1944 | 0 | mprintf("Retrieving %s\n", src); |
1945 | |
|
1946 | 0 | if (dirattrib->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) { |
1947 | 0 | mode = dirattrib->perm & 01777; |
1948 | 0 | tmpmode = mode | (S_IWUSR|S_IXUSR); |
1949 | 0 | } else { |
1950 | 0 | debug("download remote \"%s\": server " |
1951 | 0 | "did not send permissions", dst); |
1952 | 0 | } |
1953 | |
|
1954 | 0 | if (mkdir(dst, tmpmode) == -1 && errno != EEXIST) { |
1955 | 0 | error("mkdir %s: %s", dst, strerror(errno)); |
1956 | 0 | return -1; |
1957 | 0 | } |
1958 | | |
1959 | 0 | if (sftp_readdir(conn, src, &dir_entries) == -1) { |
1960 | 0 | error("remote readdir \"%s\" failed", src); |
1961 | 0 | return -1; |
1962 | 0 | } |
1963 | | |
1964 | 0 | for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { |
1965 | 0 | free(new_dst); |
1966 | 0 | free(new_src); |
1967 | |
|
1968 | 0 | filename = dir_entries[i]->filename; |
1969 | 0 | new_dst = sftp_path_append(dst, filename); |
1970 | 0 | new_src = sftp_path_append(src, filename); |
1971 | |
|
1972 | 0 | a = &dir_entries[i]->a; |
1973 | 0 | if (S_ISLNK(a->perm)) { |
1974 | 0 | if (!follow_link_flag) { |
1975 | 0 | logit("download \"%s\": not a regular file", |
1976 | 0 | new_src); |
1977 | 0 | continue; |
1978 | 0 | } |
1979 | | /* Replace the stat contents with the symlink target */ |
1980 | 0 | if (sftp_stat(conn, new_src, 1, &lsym) != 0) { |
1981 | 0 | logit("remote stat \"%s\" failed", new_src); |
1982 | 0 | ret = -1; |
1983 | 0 | continue; |
1984 | 0 | } |
1985 | 0 | a = &lsym; |
1986 | 0 | } |
1987 | | |
1988 | 0 | if (S_ISDIR(a->perm)) { |
1989 | 0 | if (strcmp(filename, ".") == 0 || |
1990 | 0 | strcmp(filename, "..") == 0) |
1991 | 0 | continue; |
1992 | 0 | if (download_dir_internal(conn, new_src, new_dst, |
1993 | 0 | depth + 1, a, preserve_flag, |
1994 | 0 | print_flag, resume_flag, |
1995 | 0 | fsync_flag, follow_link_flag, inplace_flag) == -1) |
1996 | 0 | ret = -1; |
1997 | 0 | } else if (S_ISREG(a->perm)) { |
1998 | 0 | if (sftp_download(conn, new_src, new_dst, a, |
1999 | 0 | preserve_flag, resume_flag, fsync_flag, |
2000 | 0 | inplace_flag) == -1) { |
2001 | 0 | error("Download of file %s to %s failed", |
2002 | 0 | new_src, new_dst); |
2003 | 0 | ret = -1; |
2004 | 0 | } |
2005 | 0 | } else |
2006 | 0 | logit("download \"%s\": not a regular file", new_src); |
2007 | |
|
2008 | 0 | } |
2009 | 0 | free(new_dst); |
2010 | 0 | free(new_src); |
2011 | |
|
2012 | 0 | if (preserve_flag) { |
2013 | 0 | if (dirattrib->flags & SSH2_FILEXFER_ATTR_ACMODTIME) { |
2014 | 0 | struct timeval tv[2]; |
2015 | 0 | tv[0].tv_sec = dirattrib->atime; |
2016 | 0 | tv[1].tv_sec = dirattrib->mtime; |
2017 | 0 | tv[0].tv_usec = tv[1].tv_usec = 0; |
2018 | 0 | if (utimes(dst, tv) == -1) |
2019 | 0 | error("local set times on \"%s\": %s", |
2020 | 0 | dst, strerror(errno)); |
2021 | 0 | } else |
2022 | 0 | debug("Server did not send times for directory " |
2023 | 0 | "\"%s\"", dst); |
2024 | 0 | } |
2025 | |
|
2026 | 0 | if (mode != tmpmode && chmod(dst, mode) == -1) |
2027 | 0 | error("local chmod directory \"%s\": %s", dst, |
2028 | 0 | strerror(errno)); |
2029 | |
|
2030 | 0 | sftp_free_dirents(dir_entries); |
2031 | |
|
2032 | 0 | return ret; |
2033 | 0 | } |
2034 | | |
2035 | | int |
2036 | | sftp_download_dir(struct sftp_conn *conn, const char *src, const char *dst, |
2037 | | Attrib *dirattrib, int preserve_flag, int print_flag, int resume_flag, |
2038 | | int fsync_flag, int follow_link_flag, int inplace_flag) |
2039 | 0 | { |
2040 | 0 | char *src_canon; |
2041 | 0 | int ret; |
2042 | |
|
2043 | 0 | if ((src_canon = sftp_realpath(conn, src)) == NULL) { |
2044 | 0 | error("download \"%s\": path canonicalization failed", src); |
2045 | 0 | return -1; |
2046 | 0 | } |
2047 | | |
2048 | 0 | ret = download_dir_internal(conn, src_canon, dst, 0, |
2049 | 0 | dirattrib, preserve_flag, print_flag, resume_flag, fsync_flag, |
2050 | 0 | follow_link_flag, inplace_flag); |
2051 | 0 | free(src_canon); |
2052 | 0 | return ret; |
2053 | 0 | } |
2054 | | |
2055 | | int |
2056 | | sftp_upload(struct sftp_conn *conn, const char *local_path, |
2057 | | const char *remote_path, int preserve_flag, int resume, |
2058 | | int fsync_flag, int inplace_flag) |
2059 | 0 | { |
2060 | 0 | int r, local_fd; |
2061 | 0 | u_int openmode, id, status = SSH2_FX_OK, status2, reordered = 0; |
2062 | 0 | off_t offset, progress_counter; |
2063 | 0 | u_char type, *handle, *data; |
2064 | 0 | struct sshbuf *msg; |
2065 | 0 | struct stat sb; |
2066 | 0 | Attrib a, t, c; |
2067 | 0 | uint32_t startid, ackid; |
2068 | 0 | uint64_t highwater = 0, maxack = 0; |
2069 | 0 | struct request *ack = NULL; |
2070 | 0 | struct requests acks; |
2071 | 0 | size_t handle_len; |
2072 | |
|
2073 | 0 | debug2_f("upload local \"%s\" to remote \"%s\"", |
2074 | 0 | local_path, remote_path); |
2075 | |
|
2076 | 0 | TAILQ_INIT(&acks); |
2077 | |
|
2078 | 0 | if ((local_fd = open(local_path, O_RDONLY)) == -1) { |
2079 | 0 | error("open local \"%s\": %s", local_path, strerror(errno)); |
2080 | 0 | return(-1); |
2081 | 0 | } |
2082 | 0 | if (fstat(local_fd, &sb) == -1) { |
2083 | 0 | error("fstat local \"%s\": %s", local_path, strerror(errno)); |
2084 | 0 | close(local_fd); |
2085 | 0 | return(-1); |
2086 | 0 | } |
2087 | 0 | if (!S_ISREG(sb.st_mode)) { |
2088 | 0 | error("local \"%s\" is not a regular file", local_path); |
2089 | 0 | close(local_fd); |
2090 | 0 | return(-1); |
2091 | 0 | } |
2092 | 0 | stat_to_attrib(&sb, &a); |
2093 | |
|
2094 | 0 | a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; |
2095 | 0 | a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; |
2096 | 0 | a.perm &= 0777; |
2097 | 0 | if (!preserve_flag) |
2098 | 0 | a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; |
2099 | |
|
2100 | 0 | if (resume) { |
2101 | | /* Get remote file size if it exists */ |
2102 | 0 | if (sftp_stat(conn, remote_path, 0, &c) != 0) { |
2103 | 0 | close(local_fd); |
2104 | 0 | return -1; |
2105 | 0 | } |
2106 | | |
2107 | 0 | if ((off_t)c.size >= sb.st_size) { |
2108 | 0 | error("resume \"%s\": destination file " |
2109 | 0 | "same size or larger", local_path); |
2110 | 0 | close(local_fd); |
2111 | 0 | return -1; |
2112 | 0 | } |
2113 | | |
2114 | 0 | if (lseek(local_fd, (off_t)c.size, SEEK_SET) == -1) { |
2115 | 0 | close(local_fd); |
2116 | 0 | return -1; |
2117 | 0 | } |
2118 | 0 | highwater = c.size; |
2119 | 0 | } |
2120 | | |
2121 | 0 | openmode = SSH2_FXF_WRITE|SSH2_FXF_CREAT; |
2122 | 0 | if (resume) |
2123 | 0 | openmode |= SSH2_FXF_APPEND; |
2124 | 0 | else if (!inplace_flag) |
2125 | 0 | openmode |= SSH2_FXF_TRUNC; |
2126 | | |
2127 | | /* Send open request */ |
2128 | 0 | if (send_open(conn, remote_path, "dest", openmode, &a, |
2129 | 0 | &handle, &handle_len) != 0) { |
2130 | 0 | close(local_fd); |
2131 | 0 | return -1; |
2132 | 0 | } |
2133 | | |
2134 | 0 | id = conn->msg_id; |
2135 | 0 | startid = ackid = id + 1; |
2136 | 0 | data = xmalloc(conn->upload_buflen); |
2137 | | |
2138 | | /* Read from local and write to remote */ |
2139 | 0 | offset = progress_counter = (resume ? c.size : 0); |
2140 | 0 | if (showprogress) { |
2141 | 0 | start_progress_meter(progress_meter_path(local_path), |
2142 | 0 | sb.st_size, &progress_counter); |
2143 | 0 | } |
2144 | |
|
2145 | 0 | if ((msg = sshbuf_new()) == NULL) |
2146 | 0 | fatal_f("sshbuf_new failed"); |
2147 | 0 | for (;;) { |
2148 | 0 | int len; |
2149 | | |
2150 | | /* |
2151 | | * Can't use atomicio here because it returns 0 on EOF, |
2152 | | * thus losing the last block of the file. |
2153 | | * Simulate an EOF on interrupt, allowing ACKs from the |
2154 | | * server to drain. |
2155 | | */ |
2156 | 0 | if (interrupted || status != SSH2_FX_OK) |
2157 | 0 | len = 0; |
2158 | 0 | else do |
2159 | 0 | len = read(local_fd, data, conn->upload_buflen); |
2160 | 0 | while ((len == -1) && |
2161 | 0 | (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK)); |
2162 | |
|
2163 | 0 | if (len == -1) { |
2164 | 0 | fatal("read local \"%s\": %s", |
2165 | 0 | local_path, strerror(errno)); |
2166 | 0 | } else if (len != 0) { |
2167 | 0 | ack = request_enqueue(&acks, ++id, len, offset); |
2168 | 0 | sshbuf_reset(msg); |
2169 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 || |
2170 | 0 | (r = sshbuf_put_u32(msg, ack->id)) != 0 || |
2171 | 0 | (r = sshbuf_put_string(msg, handle, |
2172 | 0 | handle_len)) != 0 || |
2173 | 0 | (r = sshbuf_put_u64(msg, offset)) != 0 || |
2174 | 0 | (r = sshbuf_put_string(msg, data, len)) != 0) |
2175 | 0 | fatal_fr(r, "compose"); |
2176 | 0 | send_msg(conn, msg); |
2177 | 0 | debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%u", |
2178 | 0 | id, (unsigned long long)offset, len); |
2179 | 0 | } else if (TAILQ_FIRST(&acks) == NULL) |
2180 | 0 | break; |
2181 | | |
2182 | 0 | if (ack == NULL) |
2183 | 0 | fatal("Unexpected ACK %u", id); |
2184 | | |
2185 | 0 | if (id == startid || len == 0 || |
2186 | 0 | id - ackid >= conn->num_requests) { |
2187 | 0 | u_int rid; |
2188 | |
|
2189 | 0 | sshbuf_reset(msg); |
2190 | 0 | get_msg(conn, msg); |
2191 | 0 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
2192 | 0 | (r = sshbuf_get_u32(msg, &rid)) != 0) |
2193 | 0 | fatal_fr(r, "parse"); |
2194 | | |
2195 | 0 | if (type != SSH2_FXP_STATUS) |
2196 | 0 | fatal("Expected SSH2_FXP_STATUS(%d) packet, " |
2197 | 0 | "got %d", SSH2_FXP_STATUS, type); |
2198 | | |
2199 | 0 | if ((r = sshbuf_get_u32(msg, &status2)) != 0) |
2200 | 0 | fatal_fr(r, "parse status"); |
2201 | 0 | debug3("SSH2_FXP_STATUS %u", status2); |
2202 | 0 | if (status2 != SSH2_FX_OK) |
2203 | 0 | status = status2; /* remember errors */ |
2204 | | |
2205 | | /* Find the request in our queue */ |
2206 | 0 | if ((ack = request_find(&acks, rid)) == NULL) |
2207 | 0 | fatal("Can't find request for ID %u", rid); |
2208 | 0 | TAILQ_REMOVE(&acks, ack, tq); |
2209 | 0 | debug3("In write loop, ack for %u %zu bytes at %lld", |
2210 | 0 | ack->id, ack->len, (unsigned long long)ack->offset); |
2211 | 0 | ++ackid; |
2212 | 0 | progress_counter += ack->len; |
2213 | | /* |
2214 | | * Track both the highest offset acknowledged and the |
2215 | | * highest *contiguous* offset acknowledged. |
2216 | | * We'll need the latter for ftruncate()ing |
2217 | | * interrupted transfers. |
2218 | | */ |
2219 | 0 | if (maxack < ack->offset + ack->len) |
2220 | 0 | maxack = ack->offset + ack->len; |
2221 | 0 | if (!reordered && ack->offset <= highwater) |
2222 | 0 | highwater = maxack; |
2223 | 0 | else if (!reordered && ack->offset > highwater) { |
2224 | 0 | debug3_f("server reordered ACKs"); |
2225 | 0 | reordered = 1; |
2226 | 0 | } |
2227 | 0 | free(ack); |
2228 | 0 | } |
2229 | 0 | offset += len; |
2230 | 0 | if (offset < 0) |
2231 | 0 | fatal_f("offset < 0"); |
2232 | 0 | } |
2233 | 0 | sshbuf_free(msg); |
2234 | |
|
2235 | 0 | if (showprogress) |
2236 | 0 | stop_progress_meter(); |
2237 | 0 | free(data); |
2238 | |
|
2239 | 0 | if (status == SSH2_FX_OK && !interrupted) { |
2240 | | /* we got everything */ |
2241 | 0 | highwater = maxack; |
2242 | 0 | } |
2243 | 0 | if (status != SSH2_FX_OK) { |
2244 | 0 | error("write remote \"%s\": %s", remote_path, fx2txt(status)); |
2245 | 0 | status = SSH2_FX_FAILURE; |
2246 | 0 | } |
2247 | |
|
2248 | 0 | if (inplace_flag || (resume && (status != SSH2_FX_OK || interrupted))) { |
2249 | 0 | debug("truncating at %llu", (unsigned long long)highwater); |
2250 | 0 | attrib_clear(&t); |
2251 | 0 | t.flags = SSH2_FILEXFER_ATTR_SIZE; |
2252 | 0 | t.size = highwater; |
2253 | 0 | sftp_fsetstat(conn, handle, handle_len, &t); |
2254 | 0 | } |
2255 | |
|
2256 | 0 | if (close(local_fd) == -1) { |
2257 | 0 | error("close local \"%s\": %s", local_path, strerror(errno)); |
2258 | 0 | status = SSH2_FX_FAILURE; |
2259 | 0 | } |
2260 | | |
2261 | | /* Override umask and utimes if asked */ |
2262 | 0 | if (preserve_flag) |
2263 | 0 | sftp_fsetstat(conn, handle, handle_len, &a); |
2264 | |
|
2265 | 0 | if (fsync_flag) |
2266 | 0 | (void)sftp_fsync(conn, handle, handle_len); |
2267 | |
|
2268 | 0 | if (sftp_close(conn, handle, handle_len) != 0) |
2269 | 0 | status = SSH2_FX_FAILURE; |
2270 | |
|
2271 | 0 | free(handle); |
2272 | |
|
2273 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
2274 | 0 | } |
2275 | | |
2276 | | static int |
2277 | | upload_dir_internal(struct sftp_conn *conn, const char *src, const char *dst, |
2278 | | int depth, int preserve_flag, int print_flag, int resume, int fsync_flag, |
2279 | | int follow_link_flag, int inplace_flag) |
2280 | 0 | { |
2281 | 0 | int created = 0, ret = 0; |
2282 | 0 | DIR *dirp; |
2283 | 0 | struct dirent *dp; |
2284 | 0 | char *filename, *new_src = NULL, *new_dst = NULL; |
2285 | 0 | struct stat sb; |
2286 | 0 | Attrib a, dirattrib; |
2287 | 0 | uint32_t saved_perm; |
2288 | |
|
2289 | 0 | debug2_f("upload local dir \"%s\" to remote \"%s\"", src, dst); |
2290 | |
|
2291 | 0 | if (depth >= MAX_DIR_DEPTH) { |
2292 | 0 | error("Maximum directory depth exceeded: %d levels", depth); |
2293 | 0 | return -1; |
2294 | 0 | } |
2295 | | |
2296 | 0 | if (stat(src, &sb) == -1) { |
2297 | 0 | error("stat local \"%s\": %s", src, strerror(errno)); |
2298 | 0 | return -1; |
2299 | 0 | } |
2300 | 0 | if (!S_ISDIR(sb.st_mode)) { |
2301 | 0 | error("\"%s\" is not a directory", src); |
2302 | 0 | return -1; |
2303 | 0 | } |
2304 | 0 | if (print_flag && print_flag != SFTP_PROGRESS_ONLY) |
2305 | 0 | mprintf("Entering %s\n", src); |
2306 | |
|
2307 | 0 | stat_to_attrib(&sb, &a); |
2308 | 0 | a.flags &= ~SSH2_FILEXFER_ATTR_SIZE; |
2309 | 0 | a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; |
2310 | 0 | a.perm &= 01777; |
2311 | 0 | if (!preserve_flag) |
2312 | 0 | a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; |
2313 | | |
2314 | | /* |
2315 | | * sftp lacks a portable status value to match errno EEXIST, |
2316 | | * so if we get a failure back then we must check whether |
2317 | | * the path already existed and is a directory. Ensure we can |
2318 | | * write to the directory we create for the duration of the transfer. |
2319 | | */ |
2320 | 0 | saved_perm = a.perm; |
2321 | 0 | a.perm |= (S_IWUSR|S_IXUSR); |
2322 | 0 | if (sftp_mkdir(conn, dst, &a, 0) == 0) |
2323 | 0 | created = 1; |
2324 | 0 | else { |
2325 | 0 | if (sftp_stat(conn, dst, 0, &dirattrib) != 0) |
2326 | 0 | return -1; |
2327 | 0 | if (!S_ISDIR(dirattrib.perm)) { |
2328 | 0 | error("\"%s\" exists but is not a directory", dst); |
2329 | 0 | return -1; |
2330 | 0 | } |
2331 | 0 | } |
2332 | 0 | a.perm = saved_perm; |
2333 | |
|
2334 | 0 | if ((dirp = opendir(src)) == NULL) { |
2335 | 0 | error("local opendir \"%s\": %s", src, strerror(errno)); |
2336 | 0 | return -1; |
2337 | 0 | } |
2338 | | |
2339 | 0 | while (((dp = readdir(dirp)) != NULL) && !interrupted) { |
2340 | 0 | if (dp->d_ino == 0) |
2341 | 0 | continue; |
2342 | 0 | free(new_dst); |
2343 | 0 | free(new_src); |
2344 | 0 | filename = dp->d_name; |
2345 | 0 | new_dst = sftp_path_append(dst, filename); |
2346 | 0 | new_src = sftp_path_append(src, filename); |
2347 | |
|
2348 | 0 | if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) |
2349 | 0 | continue; |
2350 | 0 | if (lstat(new_src, &sb) == -1) { |
2351 | 0 | logit("local lstat \"%s\": %s", filename, |
2352 | 0 | strerror(errno)); |
2353 | 0 | ret = -1; |
2354 | 0 | continue; |
2355 | 0 | } |
2356 | 0 | if (S_ISLNK(sb.st_mode)) { |
2357 | 0 | if (!follow_link_flag) { |
2358 | 0 | logit("%s: not a regular file", filename); |
2359 | 0 | continue; |
2360 | 0 | } |
2361 | | /* Replace the stat contents with the symlink target */ |
2362 | 0 | if (stat(new_src, &sb) == -1) { |
2363 | 0 | logit("local stat \"%s\": %s", filename, |
2364 | 0 | strerror(errno)); |
2365 | 0 | ret = -1; |
2366 | 0 | continue; |
2367 | 0 | } |
2368 | 0 | } |
2369 | 0 | if (S_ISDIR(sb.st_mode)) { |
2370 | 0 | if (upload_dir_internal(conn, new_src, new_dst, |
2371 | 0 | depth + 1, preserve_flag, print_flag, resume, |
2372 | 0 | fsync_flag, follow_link_flag, inplace_flag) == -1) |
2373 | 0 | ret = -1; |
2374 | 0 | } else if (S_ISREG(sb.st_mode)) { |
2375 | 0 | if (sftp_upload(conn, new_src, new_dst, |
2376 | 0 | preserve_flag, resume, fsync_flag, |
2377 | 0 | inplace_flag) == -1) { |
2378 | 0 | error("upload \"%s\" to \"%s\" failed", |
2379 | 0 | new_src, new_dst); |
2380 | 0 | ret = -1; |
2381 | 0 | } |
2382 | 0 | } else |
2383 | 0 | logit("%s: not a regular file", filename); |
2384 | 0 | } |
2385 | 0 | free(new_dst); |
2386 | 0 | free(new_src); |
2387 | |
|
2388 | 0 | if (created || preserve_flag) |
2389 | 0 | sftp_setstat(conn, dst, &a); |
2390 | |
|
2391 | 0 | (void) closedir(dirp); |
2392 | 0 | return ret; |
2393 | 0 | } |
2394 | | |
2395 | | int |
2396 | | sftp_upload_dir(struct sftp_conn *conn, const char *src, const char *dst, |
2397 | | int preserve_flag, int print_flag, int resume, int fsync_flag, |
2398 | | int follow_link_flag, int inplace_flag) |
2399 | 0 | { |
2400 | 0 | char *dst_canon; |
2401 | 0 | int ret; |
2402 | |
|
2403 | 0 | if ((dst_canon = sftp_realpath(conn, dst)) == NULL) { |
2404 | 0 | error("upload \"%s\": path canonicalization failed", dst); |
2405 | 0 | return -1; |
2406 | 0 | } |
2407 | | |
2408 | 0 | ret = upload_dir_internal(conn, src, dst_canon, 0, preserve_flag, |
2409 | 0 | print_flag, resume, fsync_flag, follow_link_flag, inplace_flag); |
2410 | |
|
2411 | 0 | free(dst_canon); |
2412 | 0 | return ret; |
2413 | 0 | } |
2414 | | |
2415 | | static void |
2416 | | handle_dest_replies(struct sftp_conn *to, const char *to_path, int synchronous, |
2417 | | u_int *nreqsp, u_int *write_errorp) |
2418 | 0 | { |
2419 | 0 | struct sshbuf *msg; |
2420 | 0 | u_char type; |
2421 | 0 | u_int id, status; |
2422 | 0 | int r; |
2423 | 0 | struct pollfd pfd; |
2424 | |
|
2425 | 0 | if ((msg = sshbuf_new()) == NULL) |
2426 | 0 | fatal_f("sshbuf_new failed"); |
2427 | | |
2428 | | /* Try to eat replies from the upload side */ |
2429 | 0 | while (*nreqsp > 0) { |
2430 | 0 | debug3_f("%u outstanding replies", *nreqsp); |
2431 | 0 | if (!synchronous) { |
2432 | | /* Bail out if no data is ready to be read */ |
2433 | 0 | pfd.fd = to->fd_in; |
2434 | 0 | pfd.events = POLLIN; |
2435 | 0 | if ((r = poll(&pfd, 1, 0)) == -1) { |
2436 | 0 | if (errno == EINTR) |
2437 | 0 | break; |
2438 | 0 | fatal_f("poll: %s", strerror(errno)); |
2439 | 0 | } else if (r == 0) |
2440 | 0 | break; /* fd not ready */ |
2441 | 0 | } |
2442 | 0 | sshbuf_reset(msg); |
2443 | 0 | get_msg(to, msg); |
2444 | |
|
2445 | 0 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
2446 | 0 | (r = sshbuf_get_u32(msg, &id)) != 0) |
2447 | 0 | fatal_fr(r, "dest parse"); |
2448 | 0 | debug3("Received dest reply T:%u I:%u R:%u", type, id, *nreqsp); |
2449 | 0 | if (type != SSH2_FXP_STATUS) { |
2450 | 0 | fatal_f("Expected SSH2_FXP_STATUS(%d) packet, got %d", |
2451 | 0 | SSH2_FXP_STATUS, type); |
2452 | 0 | } |
2453 | 0 | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
2454 | 0 | fatal_fr(r, "parse dest status"); |
2455 | 0 | debug3("dest SSH2_FXP_STATUS %u", status); |
2456 | 0 | if (status != SSH2_FX_OK) { |
2457 | | /* record first error */ |
2458 | 0 | if (*write_errorp == 0) |
2459 | 0 | *write_errorp = status; |
2460 | 0 | } |
2461 | | /* |
2462 | | * XXX this doesn't do full reply matching like sftp_upload and |
2463 | | * so cannot gracefully truncate terminated uploads at a |
2464 | | * high-water mark. ATM the only caller of this function (scp) |
2465 | | * doesn't support transfer resumption, so this doesn't matter |
2466 | | * a whole lot. |
2467 | | * |
2468 | | * To be safe, sftp_crossload truncates the destination file to |
2469 | | * zero length on upload failure, since we can't trust the |
2470 | | * server not to have reordered replies that could have |
2471 | | * inserted holes where none existed in the source file. |
2472 | | * |
2473 | | * XXX we could get a more accurate progress bar if we updated |
2474 | | * the counter based on the reply from the destination... |
2475 | | */ |
2476 | 0 | (*nreqsp)--; |
2477 | 0 | } |
2478 | 0 | debug3_f("done: %u outstanding replies", *nreqsp); |
2479 | 0 | sshbuf_free(msg); |
2480 | 0 | } |
2481 | | |
2482 | | int |
2483 | | sftp_crossload(struct sftp_conn *from, struct sftp_conn *to, |
2484 | | const char *from_path, const char *to_path, |
2485 | | Attrib *a, int preserve_flag) |
2486 | 0 | { |
2487 | 0 | struct sshbuf *msg; |
2488 | 0 | int write_error, read_error, r, seen_zerolen = 0; |
2489 | 0 | uint64_t offset = 0, size; |
2490 | 0 | u_int id, buflen, num_req, max_req, status = SSH2_FX_OK; |
2491 | 0 | u_int num_upload_req; |
2492 | 0 | off_t progress_counter; |
2493 | 0 | u_char *from_handle, *to_handle; |
2494 | 0 | size_t from_handle_len, to_handle_len; |
2495 | 0 | struct requests requests; |
2496 | 0 | struct request *req; |
2497 | 0 | u_char type; |
2498 | 0 | Attrib attr; |
2499 | |
|
2500 | 0 | debug2_f("crossload src \"%s\" to dst \"%s\"", from_path, to_path); |
2501 | |
|
2502 | 0 | TAILQ_INIT(&requests); |
2503 | |
|
2504 | 0 | if (a == NULL) { |
2505 | 0 | if (sftp_stat(from, from_path, 0, &attr) != 0) |
2506 | 0 | return -1; |
2507 | 0 | a = &attr; |
2508 | 0 | } |
2509 | | |
2510 | 0 | if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) && |
2511 | 0 | (!S_ISREG(a->perm))) { |
2512 | 0 | error("download \"%s\": not a regular file", from_path); |
2513 | 0 | return(-1); |
2514 | 0 | } |
2515 | 0 | if (a->flags & SSH2_FILEXFER_ATTR_SIZE) |
2516 | 0 | size = a->size; |
2517 | 0 | else |
2518 | 0 | size = 0; |
2519 | |
|
2520 | 0 | buflen = from->download_buflen; |
2521 | 0 | if (buflen > to->upload_buflen) |
2522 | 0 | buflen = to->upload_buflen; |
2523 | | |
2524 | | /* Send open request to read side */ |
2525 | 0 | if (send_open(from, from_path, "origin", SSH2_FXF_READ, NULL, |
2526 | 0 | &from_handle, &from_handle_len) != 0) |
2527 | 0 | return -1; |
2528 | | |
2529 | | /* Send open request to write side */ |
2530 | 0 | a->flags &= ~SSH2_FILEXFER_ATTR_SIZE; |
2531 | 0 | a->flags &= ~SSH2_FILEXFER_ATTR_UIDGID; |
2532 | 0 | a->perm &= 0777; |
2533 | 0 | if (!preserve_flag) |
2534 | 0 | a->flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME; |
2535 | 0 | if (send_open(to, to_path, "dest", |
2536 | 0 | SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, |
2537 | 0 | &to_handle, &to_handle_len) != 0) { |
2538 | 0 | sftp_close(from, from_handle, from_handle_len); |
2539 | 0 | return -1; |
2540 | 0 | } |
2541 | | |
2542 | | /* Read from remote "from" and write to remote "to" */ |
2543 | 0 | offset = 0; |
2544 | 0 | write_error = read_error = num_req = num_upload_req = 0; |
2545 | 0 | max_req = 1; |
2546 | 0 | progress_counter = 0; |
2547 | |
|
2548 | 0 | if (showprogress && size != 0) { |
2549 | 0 | start_progress_meter(progress_meter_path(from_path), |
2550 | 0 | size, &progress_counter); |
2551 | 0 | } |
2552 | 0 | if ((msg = sshbuf_new()) == NULL) |
2553 | 0 | fatal_f("sshbuf_new failed"); |
2554 | 0 | while (num_req > 0 || max_req > 0) { |
2555 | 0 | u_char *data; |
2556 | 0 | size_t len; |
2557 | | |
2558 | | /* |
2559 | | * Simulate EOF on interrupt: stop sending new requests and |
2560 | | * allow outstanding requests to drain gracefully |
2561 | | */ |
2562 | 0 | if (interrupted) { |
2563 | 0 | if (num_req == 0) /* If we haven't started yet... */ |
2564 | 0 | break; |
2565 | 0 | max_req = 0; |
2566 | 0 | } |
2567 | | |
2568 | | /* Send some more requests */ |
2569 | 0 | while (num_req < max_req) { |
2570 | 0 | debug3("Request range %llu -> %llu (%d/%d)", |
2571 | 0 | (unsigned long long)offset, |
2572 | 0 | (unsigned long long)offset + buflen - 1, |
2573 | 0 | num_req, max_req); |
2574 | 0 | req = request_enqueue(&requests, from->msg_id++, |
2575 | 0 | buflen, offset); |
2576 | 0 | offset += buflen; |
2577 | 0 | num_req++; |
2578 | 0 | send_read_request(from, req->id, req->offset, |
2579 | 0 | req->len, from_handle, from_handle_len); |
2580 | 0 | } |
2581 | | |
2582 | | /* Try to eat replies from the upload side (nonblocking) */ |
2583 | 0 | handle_dest_replies(to, to_path, 0, |
2584 | 0 | &num_upload_req, &write_error); |
2585 | |
|
2586 | 0 | sshbuf_reset(msg); |
2587 | 0 | get_msg(from, msg); |
2588 | 0 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
2589 | 0 | (r = sshbuf_get_u32(msg, &id)) != 0) |
2590 | 0 | fatal_fr(r, "parse"); |
2591 | 0 | debug3("Received origin reply T:%u I:%u R:%d", |
2592 | 0 | type, id, max_req); |
2593 | | |
2594 | | /* Find the request in our queue */ |
2595 | 0 | if ((req = request_find(&requests, id)) == NULL) |
2596 | 0 | fatal("Unexpected reply %u", id); |
2597 | | |
2598 | 0 | switch (type) { |
2599 | 0 | case SSH2_FXP_STATUS: |
2600 | 0 | if ((r = sshbuf_get_u32(msg, &status)) != 0) |
2601 | 0 | fatal_fr(r, "parse status"); |
2602 | 0 | if (status != SSH2_FX_EOF) |
2603 | 0 | read_error = 1; |
2604 | 0 | max_req = 0; |
2605 | 0 | TAILQ_REMOVE(&requests, req, tq); |
2606 | 0 | free(req); |
2607 | 0 | num_req--; |
2608 | 0 | break; |
2609 | 0 | case SSH2_FXP_DATA: |
2610 | 0 | if ((r = sshbuf_get_string(msg, &data, &len)) != 0) |
2611 | 0 | fatal_fr(r, "parse data"); |
2612 | 0 | debug3("Received data %llu -> %llu", |
2613 | 0 | (unsigned long long)req->offset, |
2614 | 0 | (unsigned long long)req->offset + len - 1); |
2615 | 0 | if (len > req->len) |
2616 | 0 | fatal("Received more data than asked for " |
2617 | 0 | "%zu > %zu", len, req->len); |
2618 | 0 | if (len == 0) { |
2619 | 0 | if (seen_zerolen) |
2620 | 0 | fatal_f("server sent zero data length"); |
2621 | 0 | seen_zerolen = 1; |
2622 | 0 | } |
2623 | | |
2624 | | /* Write this chunk out to the destination */ |
2625 | 0 | sshbuf_reset(msg); |
2626 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_WRITE)) != 0 || |
2627 | 0 | (r = sshbuf_put_u32(msg, to->msg_id++)) != 0 || |
2628 | 0 | (r = sshbuf_put_string(msg, to_handle, |
2629 | 0 | to_handle_len)) != 0 || |
2630 | 0 | (r = sshbuf_put_u64(msg, req->offset)) != 0 || |
2631 | 0 | (r = sshbuf_put_string(msg, data, len)) != 0) |
2632 | 0 | fatal_fr(r, "compose write"); |
2633 | 0 | send_msg(to, msg); |
2634 | 0 | debug3("Sent message SSH2_FXP_WRITE I:%u O:%llu S:%zu", |
2635 | 0 | id, (unsigned long long)offset, len); |
2636 | 0 | num_upload_req++; |
2637 | 0 | progress_counter += len; |
2638 | 0 | free(data); |
2639 | |
|
2640 | 0 | if (len == req->len) { |
2641 | 0 | TAILQ_REMOVE(&requests, req, tq); |
2642 | 0 | free(req); |
2643 | 0 | num_req--; |
2644 | 0 | } else { |
2645 | | /* Resend the request for the missing data */ |
2646 | 0 | debug3("Short data block, re-requesting " |
2647 | 0 | "%llu -> %llu (%2d)", |
2648 | 0 | (unsigned long long)req->offset + len, |
2649 | 0 | (unsigned long long)req->offset + |
2650 | 0 | req->len - 1, num_req); |
2651 | 0 | req->id = from->msg_id++; |
2652 | 0 | req->len -= len; |
2653 | 0 | req->offset += len; |
2654 | 0 | send_read_request(from, req->id, |
2655 | 0 | req->offset, req->len, |
2656 | 0 | from_handle, from_handle_len); |
2657 | | /* Reduce the request size */ |
2658 | 0 | if (len < buflen) |
2659 | 0 | buflen = MAXIMUM(MIN_READ_SIZE, len); |
2660 | 0 | } |
2661 | 0 | if (max_req > 0) { /* max_req = 0 iff EOF received */ |
2662 | 0 | if (size > 0 && offset > size) { |
2663 | | /* Only one request at a time |
2664 | | * after the expected EOF */ |
2665 | 0 | debug3("Finish at %llu (%2d)", |
2666 | 0 | (unsigned long long)offset, |
2667 | 0 | num_req); |
2668 | 0 | max_req = 1; |
2669 | 0 | } else if (max_req < from->num_requests) { |
2670 | 0 | ++max_req; |
2671 | 0 | } |
2672 | 0 | } |
2673 | 0 | break; |
2674 | 0 | default: |
2675 | 0 | fatal("Expected SSH2_FXP_DATA(%u) packet, got %u", |
2676 | 0 | SSH2_FXP_DATA, type); |
2677 | 0 | } |
2678 | 0 | } |
2679 | | |
2680 | 0 | if (showprogress && size) |
2681 | 0 | stop_progress_meter(); |
2682 | | |
2683 | | /* Drain replies from the server (blocking) */ |
2684 | 0 | debug3_f("waiting for %u replies from destination", num_upload_req); |
2685 | 0 | handle_dest_replies(to, to_path, 1, &num_upload_req, &write_error); |
2686 | | |
2687 | | /* Sanity check */ |
2688 | 0 | if (TAILQ_FIRST(&requests) != NULL) |
2689 | 0 | fatal("Transfer complete, but requests still in queue"); |
2690 | | /* Truncate at 0 length on interrupt or error to avoid holes at dest */ |
2691 | 0 | if (read_error || write_error || interrupted) { |
2692 | 0 | debug("truncating \"%s\" at 0", to_path); |
2693 | 0 | sftp_close(to, to_handle, to_handle_len); |
2694 | 0 | free(to_handle); |
2695 | 0 | if (send_open(to, to_path, "dest", |
2696 | 0 | SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC, a, |
2697 | 0 | &to_handle, &to_handle_len) != 0) { |
2698 | 0 | error("dest truncate \"%s\" failed", to_path); |
2699 | 0 | to_handle = NULL; |
2700 | 0 | } |
2701 | 0 | } |
2702 | 0 | if (read_error) { |
2703 | 0 | error("read origin \"%s\": %s", from_path, fx2txt(status)); |
2704 | 0 | status = -1; |
2705 | 0 | sftp_close(from, from_handle, from_handle_len); |
2706 | 0 | if (to_handle != NULL) |
2707 | 0 | sftp_close(to, to_handle, to_handle_len); |
2708 | 0 | } else if (write_error) { |
2709 | 0 | error("write dest \"%s\": %s", to_path, fx2txt(write_error)); |
2710 | 0 | status = SSH2_FX_FAILURE; |
2711 | 0 | sftp_close(from, from_handle, from_handle_len); |
2712 | 0 | if (to_handle != NULL) |
2713 | 0 | sftp_close(to, to_handle, to_handle_len); |
2714 | 0 | } else { |
2715 | 0 | if (sftp_close(from, from_handle, from_handle_len) != 0 || |
2716 | 0 | interrupted) |
2717 | 0 | status = -1; |
2718 | 0 | else |
2719 | 0 | status = SSH2_FX_OK; |
2720 | 0 | if (to_handle != NULL) { |
2721 | | /* Need to resend utimes after write */ |
2722 | 0 | if (preserve_flag) |
2723 | 0 | sftp_fsetstat(to, to_handle, to_handle_len, a); |
2724 | 0 | sftp_close(to, to_handle, to_handle_len); |
2725 | 0 | } |
2726 | 0 | } |
2727 | 0 | sshbuf_free(msg); |
2728 | 0 | free(from_handle); |
2729 | 0 | free(to_handle); |
2730 | |
|
2731 | 0 | return status == SSH2_FX_OK ? 0 : -1; |
2732 | 0 | } |
2733 | | |
2734 | | static int |
2735 | | crossload_dir_internal(struct sftp_conn *from, struct sftp_conn *to, |
2736 | | const char *from_path, const char *to_path, |
2737 | | int depth, Attrib *dirattrib, int preserve_flag, int print_flag, |
2738 | | int follow_link_flag) |
2739 | 0 | { |
2740 | 0 | int i, ret = 0, created = 0; |
2741 | 0 | SFTP_DIRENT **dir_entries; |
2742 | 0 | char *filename, *new_from_path = NULL, *new_to_path = NULL; |
2743 | 0 | mode_t mode = 0777; |
2744 | 0 | Attrib *a, curdir, ldirattrib, newdir, lsym; |
2745 | |
|
2746 | 0 | debug2_f("crossload dir src \"%s\" to dst \"%s\"", from_path, to_path); |
2747 | |
|
2748 | 0 | if (depth >= MAX_DIR_DEPTH) { |
2749 | 0 | error("Maximum directory depth exceeded: %d levels", depth); |
2750 | 0 | return -1; |
2751 | 0 | } |
2752 | | |
2753 | 0 | if (dirattrib == NULL) { |
2754 | 0 | if (sftp_stat(from, from_path, 1, &ldirattrib) != 0) { |
2755 | 0 | error("stat remote \"%s\" failed", from_path); |
2756 | 0 | return -1; |
2757 | 0 | } |
2758 | 0 | dirattrib = &ldirattrib; |
2759 | 0 | } |
2760 | 0 | if (!S_ISDIR(dirattrib->perm)) { |
2761 | 0 | error("\"%s\" is not a directory", from_path); |
2762 | 0 | return -1; |
2763 | 0 | } |
2764 | 0 | if (print_flag && print_flag != SFTP_PROGRESS_ONLY) |
2765 | 0 | mprintf("Retrieving %s\n", from_path); |
2766 | |
|
2767 | 0 | curdir = *dirattrib; /* dirattrib will be clobbered */ |
2768 | 0 | curdir.flags &= ~SSH2_FILEXFER_ATTR_SIZE; |
2769 | 0 | curdir.flags &= ~SSH2_FILEXFER_ATTR_UIDGID; |
2770 | 0 | if ((curdir.flags & SSH2_FILEXFER_ATTR_PERMISSIONS) == 0) { |
2771 | 0 | debug("Origin did not send permissions for " |
2772 | 0 | "directory \"%s\"", to_path); |
2773 | 0 | curdir.perm = S_IWUSR|S_IXUSR; |
2774 | 0 | curdir.flags |= SSH2_FILEXFER_ATTR_PERMISSIONS; |
2775 | 0 | } |
2776 | | /* We need to be able to write to the directory while we transfer it */ |
2777 | 0 | mode = curdir.perm & 01777; |
2778 | 0 | curdir.perm = mode | (S_IWUSR|S_IXUSR); |
2779 | | |
2780 | | /* |
2781 | | * sftp lacks a portable status value to match errno EEXIST, |
2782 | | * so if we get a failure back then we must check whether |
2783 | | * the path already existed and is a directory. Ensure we can |
2784 | | * write to the directory we create for the duration of the transfer. |
2785 | | */ |
2786 | 0 | if (sftp_mkdir(to, to_path, &curdir, 0) == 0) |
2787 | 0 | created = 1; |
2788 | 0 | else { |
2789 | 0 | if (sftp_stat(to, to_path, 0, &newdir) != 0) |
2790 | 0 | return -1; |
2791 | 0 | if (!S_ISDIR(newdir.perm)) { |
2792 | 0 | error("\"%s\" exists but is not a directory", to_path); |
2793 | 0 | return -1; |
2794 | 0 | } |
2795 | 0 | } |
2796 | 0 | curdir.perm = mode; |
2797 | |
|
2798 | 0 | if (sftp_readdir(from, from_path, &dir_entries) == -1) { |
2799 | 0 | error("origin readdir \"%s\" failed", from_path); |
2800 | 0 | return -1; |
2801 | 0 | } |
2802 | | |
2803 | 0 | for (i = 0; dir_entries[i] != NULL && !interrupted; i++) { |
2804 | 0 | free(new_from_path); |
2805 | 0 | free(new_to_path); |
2806 | |
|
2807 | 0 | filename = dir_entries[i]->filename; |
2808 | 0 | new_from_path = sftp_path_append(from_path, filename); |
2809 | 0 | new_to_path = sftp_path_append(to_path, filename); |
2810 | |
|
2811 | 0 | a = &dir_entries[i]->a; |
2812 | 0 | if (S_ISLNK(a->perm)) { |
2813 | 0 | if (!follow_link_flag) { |
2814 | 0 | logit("%s: not a regular file", filename); |
2815 | 0 | continue; |
2816 | 0 | } |
2817 | | /* Replace the stat contents with the symlink target */ |
2818 | 0 | if (sftp_stat(from, new_from_path, 1, &lsym) != 0) { |
2819 | 0 | logit("remote stat \"%s\" failed", |
2820 | 0 | new_from_path); |
2821 | 0 | ret = -1; |
2822 | 0 | continue; |
2823 | 0 | } |
2824 | 0 | a = &lsym; |
2825 | 0 | } |
2826 | 0 | if (S_ISDIR(a->perm)) { |
2827 | 0 | if (strcmp(filename, ".") == 0 || |
2828 | 0 | strcmp(filename, "..") == 0) |
2829 | 0 | continue; |
2830 | 0 | if (crossload_dir_internal(from, to, |
2831 | 0 | new_from_path, new_to_path, |
2832 | 0 | depth + 1, a, preserve_flag, |
2833 | 0 | print_flag, follow_link_flag) == -1) |
2834 | 0 | ret = -1; |
2835 | 0 | } else if (S_ISREG(a->perm)) { |
2836 | 0 | if (sftp_crossload(from, to, new_from_path, |
2837 | 0 | new_to_path, a, preserve_flag) == -1) { |
2838 | 0 | error("crossload \"%s\" to \"%s\" failed", |
2839 | 0 | new_from_path, new_to_path); |
2840 | 0 | ret = -1; |
2841 | 0 | } |
2842 | 0 | } else { |
2843 | 0 | logit("origin \"%s\": not a regular file", |
2844 | 0 | new_from_path); |
2845 | 0 | } |
2846 | 0 | } |
2847 | 0 | free(new_to_path); |
2848 | 0 | free(new_from_path); |
2849 | |
|
2850 | 0 | if (created || preserve_flag) |
2851 | 0 | sftp_setstat(to, to_path, &curdir); |
2852 | |
|
2853 | 0 | sftp_free_dirents(dir_entries); |
2854 | |
|
2855 | 0 | return ret; |
2856 | 0 | } |
2857 | | |
2858 | | int |
2859 | | sftp_crossload_dir(struct sftp_conn *from, struct sftp_conn *to, |
2860 | | const char *from_path, const char *to_path, |
2861 | | Attrib *dirattrib, int preserve_flag, int print_flag, int follow_link_flag) |
2862 | 0 | { |
2863 | 0 | char *from_path_canon; |
2864 | 0 | int ret; |
2865 | |
|
2866 | 0 | if ((from_path_canon = sftp_realpath(from, from_path)) == NULL) { |
2867 | 0 | error("crossload \"%s\": path canonicalization failed", |
2868 | 0 | from_path); |
2869 | 0 | return -1; |
2870 | 0 | } |
2871 | | |
2872 | 0 | ret = crossload_dir_internal(from, to, from_path_canon, to_path, 0, |
2873 | 0 | dirattrib, preserve_flag, print_flag, follow_link_flag); |
2874 | 0 | free(from_path_canon); |
2875 | 0 | return ret; |
2876 | 0 | } |
2877 | | |
2878 | | int |
2879 | | sftp_can_get_users_groups_by_id(struct sftp_conn *conn) |
2880 | 0 | { |
2881 | 0 | return (conn->exts & SFTP_EXT_GETUSERSGROUPS_BY_ID) != 0; |
2882 | 0 | } |
2883 | | |
2884 | | int |
2885 | | sftp_get_users_groups_by_id(struct sftp_conn *conn, |
2886 | | const u_int *uids, u_int nuids, |
2887 | | const u_int *gids, u_int ngids, |
2888 | | char ***usernamesp, char ***groupnamesp) |
2889 | 0 | { |
2890 | 0 | struct sshbuf *msg, *uidbuf, *gidbuf; |
2891 | 0 | u_int i, expected_id, id; |
2892 | 0 | char *name, **usernames = NULL, **groupnames = NULL; |
2893 | 0 | u_char type; |
2894 | 0 | int r; |
2895 | |
|
2896 | 0 | *usernamesp = *groupnamesp = NULL; |
2897 | 0 | if (!sftp_can_get_users_groups_by_id(conn)) |
2898 | 0 | return SSH_ERR_FEATURE_UNSUPPORTED; |
2899 | | |
2900 | 0 | if ((msg = sshbuf_new()) == NULL || |
2901 | 0 | (uidbuf = sshbuf_new()) == NULL || |
2902 | 0 | (gidbuf = sshbuf_new()) == NULL) |
2903 | 0 | fatal_f("sshbuf_new failed"); |
2904 | 0 | expected_id = id = conn->msg_id++; |
2905 | 0 | debug2("Sending SSH2_FXP_EXTENDED(users-groups-by-id@openssh.com)"); |
2906 | 0 | for (i = 0; i < nuids; i++) { |
2907 | 0 | if ((r = sshbuf_put_u32(uidbuf, uids[i])) != 0) |
2908 | 0 | fatal_fr(r, "compose uids"); |
2909 | 0 | } |
2910 | 0 | for (i = 0; i < ngids; i++) { |
2911 | 0 | if ((r = sshbuf_put_u32(gidbuf, gids[i])) != 0) |
2912 | 0 | fatal_fr(r, "compose gids"); |
2913 | 0 | } |
2914 | 0 | if ((r = sshbuf_put_u8(msg, SSH2_FXP_EXTENDED)) != 0 || |
2915 | 0 | (r = sshbuf_put_u32(msg, id)) != 0 || |
2916 | 0 | (r = sshbuf_put_cstring(msg, |
2917 | 0 | "users-groups-by-id@openssh.com")) != 0 || |
2918 | 0 | (r = sshbuf_put_stringb(msg, uidbuf)) != 0 || |
2919 | 0 | (r = sshbuf_put_stringb(msg, gidbuf)) != 0) |
2920 | 0 | fatal_fr(r, "compose"); |
2921 | 0 | send_msg(conn, msg); |
2922 | 0 | get_msg(conn, msg); |
2923 | 0 | if ((r = sshbuf_get_u8(msg, &type)) != 0 || |
2924 | 0 | (r = sshbuf_get_u32(msg, &id)) != 0) |
2925 | 0 | fatal_fr(r, "parse"); |
2926 | 0 | if (id != expected_id) |
2927 | 0 | fatal("ID mismatch (%u != %u)", id, expected_id); |
2928 | 0 | if (type == SSH2_FXP_STATUS) { |
2929 | 0 | u_int status; |
2930 | 0 | char *errmsg; |
2931 | |
|
2932 | 0 | if ((r = sshbuf_get_u32(msg, &status)) != 0 || |
2933 | 0 | (r = sshbuf_get_cstring(msg, &errmsg, NULL)) != 0) |
2934 | 0 | fatal_fr(r, "parse status"); |
2935 | 0 | error("users-groups-by-id %s", |
2936 | 0 | *errmsg == '\0' ? fx2txt(status) : errmsg); |
2937 | 0 | free(errmsg); |
2938 | 0 | sshbuf_free(msg); |
2939 | 0 | sshbuf_free(uidbuf); |
2940 | 0 | sshbuf_free(gidbuf); |
2941 | 0 | return -1; |
2942 | 0 | } else if (type != SSH2_FXP_EXTENDED_REPLY) |
2943 | 0 | fatal("Expected SSH2_FXP_EXTENDED_REPLY(%u) packet, got %u", |
2944 | 0 | SSH2_FXP_EXTENDED_REPLY, type); |
2945 | | |
2946 | | /* reuse */ |
2947 | 0 | sshbuf_free(uidbuf); |
2948 | 0 | sshbuf_free(gidbuf); |
2949 | 0 | uidbuf = gidbuf = NULL; |
2950 | 0 | if ((r = sshbuf_froms(msg, &uidbuf)) != 0 || |
2951 | 0 | (r = sshbuf_froms(msg, &gidbuf)) != 0) |
2952 | 0 | fatal_fr(r, "parse response"); |
2953 | 0 | if (nuids > 0) { |
2954 | 0 | usernames = xcalloc(nuids, sizeof(*usernames)); |
2955 | 0 | for (i = 0; i < nuids; i++) { |
2956 | 0 | if ((r = sshbuf_get_cstring(uidbuf, &name, NULL)) != 0) |
2957 | 0 | fatal_fr(r, "parse user name"); |
2958 | | /* Handle unresolved names */ |
2959 | 0 | if (*name == '\0') { |
2960 | 0 | free(name); |
2961 | 0 | name = NULL; |
2962 | 0 | } |
2963 | 0 | usernames[i] = name; |
2964 | 0 | } |
2965 | 0 | } |
2966 | 0 | if (ngids > 0) { |
2967 | 0 | groupnames = xcalloc(ngids, sizeof(*groupnames)); |
2968 | 0 | for (i = 0; i < ngids; i++) { |
2969 | 0 | if ((r = sshbuf_get_cstring(gidbuf, &name, NULL)) != 0) |
2970 | 0 | fatal_fr(r, "parse user name"); |
2971 | | /* Handle unresolved names */ |
2972 | 0 | if (*name == '\0') { |
2973 | 0 | free(name); |
2974 | 0 | name = NULL; |
2975 | 0 | } |
2976 | 0 | groupnames[i] = name; |
2977 | 0 | } |
2978 | 0 | } |
2979 | 0 | if (sshbuf_len(uidbuf) != 0) |
2980 | 0 | fatal_f("unexpected extra username data"); |
2981 | 0 | if (sshbuf_len(gidbuf) != 0) |
2982 | 0 | fatal_f("unexpected extra groupname data"); |
2983 | 0 | sshbuf_free(uidbuf); |
2984 | 0 | sshbuf_free(gidbuf); |
2985 | 0 | sshbuf_free(msg); |
2986 | | /* success */ |
2987 | 0 | *usernamesp = usernames; |
2988 | 0 | *groupnamesp = groupnames; |
2989 | 0 | return 0; |
2990 | 0 | } |
2991 | | |
2992 | | char * |
2993 | | sftp_path_append(const char *p1, const char *p2) |
2994 | 0 | { |
2995 | 0 | char *ret; |
2996 | 0 | size_t len = strlen(p1) + strlen(p2) + 2; |
2997 | |
|
2998 | 0 | ret = xmalloc(len); |
2999 | 0 | strlcpy(ret, p1, len); |
3000 | 0 | if (p1[0] != '\0' && p1[strlen(p1) - 1] != '/') |
3001 | 0 | strlcat(ret, "/", len); |
3002 | 0 | strlcat(ret, p2, len); |
3003 | |
|
3004 | 0 | return(ret); |
3005 | 0 | } |
3006 | | |
3007 | | /* |
3008 | | * Arg p must be dynamically allocated. It will either be returned or |
3009 | | * freed and a replacement allocated. Caller must free returned string. |
3010 | | */ |
3011 | | char * |
3012 | | sftp_make_absolute(char *p, const char *pwd) |
3013 | 0 | { |
3014 | 0 | char *abs_str; |
3015 | | |
3016 | | /* Derelativise */ |
3017 | 0 | if (p && !path_absolute(p)) { |
3018 | 0 | abs_str = sftp_path_append(pwd, p); |
3019 | 0 | free(p); |
3020 | 0 | return(abs_str); |
3021 | 0 | } else |
3022 | 0 | return(p); |
3023 | 0 | } |
3024 | | |
3025 | | int |
3026 | | sftp_remote_is_dir(struct sftp_conn *conn, const char *path) |
3027 | 0 | { |
3028 | 0 | Attrib a; |
3029 | | |
3030 | | /* XXX: report errors? */ |
3031 | 0 | if (sftp_stat(conn, path, 1, &a) != 0) |
3032 | 0 | return(0); |
3033 | 0 | if (!(a.flags & SSH2_FILEXFER_ATTR_PERMISSIONS)) |
3034 | 0 | return(0); |
3035 | 0 | return S_ISDIR(a.perm); |
3036 | 0 | } |
3037 | | |
3038 | | |
3039 | | /* Check whether path returned from glob(..., GLOB_MARK, ...) is a directory */ |
3040 | | int |
3041 | | sftp_globpath_is_dir(const char *pathname) |
3042 | 0 | { |
3043 | 0 | size_t l = strlen(pathname); |
3044 | |
|
3045 | 0 | return l > 0 && pathname[l - 1] == '/'; |
3046 | 0 | } |
3047 | | |