blob: 835ae068abd33143effdc532ac8c638fb9d9b96d [file] [log] [blame]
Damien Miller33804262001-02-04 23:20:18 +11001/*
Damien Miller16a13332002-02-13 14:03:56 +11002 * Copyright (c) 2001-2002 Damien Miller. All rights reserved.
Damien Miller33804262001-02-04 23:20:18 +11003 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23 */
24
25/* XXX: memleaks */
26/* XXX: signed vs unsigned */
Damien Miller33804262001-02-04 23:20:18 +110027/* XXX: we use fatal too much, error may be more appropriate in places */
28/* XXX: copy between two remote sites */
29
30#include "includes.h"
Damien Miller16a13332002-02-13 14:03:56 +110031RCSID("$OpenBSD: sftp-client.c,v 1.21 2002/02/12 12:32:27 djm Exp $");
32
33#if defined(HAVE_SYS_QUEUE_H) && !defined(HAVE_BOGUS_SYS_QUEUE_H)
34#include <sys/queue.h>
35#else
36#include "openbsd-compat/fake-queue.h"
37#endif
Damien Miller33804262001-02-04 23:20:18 +110038
Damien Miller33804262001-02-04 23:20:18 +110039#include "buffer.h"
40#include "bufaux.h"
41#include "getput.h"
42#include "xmalloc.h"
43#include "log.h"
44#include "atomicio.h"
Damien Miller33804262001-02-04 23:20:18 +110045
46#include "sftp.h"
47#include "sftp-common.h"
48#include "sftp-client.h"
49
Damien Miller16a13332002-02-13 14:03:56 +110050/* Minimum amount of data to read at at time */
51#define MIN_READ_SIZE 512
52
Ben Lindstrom288cc392001-02-09 02:58:04 +000053/* Message ID */
54static u_int msg_id = 1;
55
Ben Lindstrombba81212001-06-25 05:01:22 +000056static void
Damien Miller33804262001-02-04 23:20:18 +110057send_msg(int fd, Buffer *m)
58{
59 int mlen = buffer_len(m);
60 int len;
61 Buffer oqueue;
62
63 buffer_init(&oqueue);
64 buffer_put_int(&oqueue, mlen);
65 buffer_append(&oqueue, buffer_ptr(m), mlen);
66 buffer_consume(m, mlen);
67
68 len = atomicio(write, fd, buffer_ptr(&oqueue), buffer_len(&oqueue));
69 if (len <= 0)
70 fatal("Couldn't send packet: %s", strerror(errno));
71
72 buffer_free(&oqueue);
73}
74
Ben Lindstrombba81212001-06-25 05:01:22 +000075static void
Damien Miller33804262001-02-04 23:20:18 +110076get_msg(int fd, Buffer *m)
77{
78 u_int len, msg_len;
79 unsigned char buf[4096];
80
81 len = atomicio(read, fd, buf, 4);
Damien Millercafff192001-03-19 22:29:46 +110082 if (len == 0)
83 fatal("Connection closed");
84 else if (len == -1)
Damien Miller33804262001-02-04 23:20:18 +110085 fatal("Couldn't read packet: %s", strerror(errno));
86
87 msg_len = GET_32BIT(buf);
88 if (msg_len > 256 * 1024)
89 fatal("Received message too long %d", msg_len);
90
91 while (msg_len) {
92 len = atomicio(read, fd, buf, MIN(msg_len, sizeof(buf)));
Damien Millercafff192001-03-19 22:29:46 +110093 if (len == 0)
94 fatal("Connection closed");
95 else if (len == -1)
Damien Miller33804262001-02-04 23:20:18 +110096 fatal("Couldn't read packet: %s", strerror(errno));
97
98 msg_len -= len;
99 buffer_append(m, buf, len);
100 }
101}
102
Ben Lindstrombba81212001-06-25 05:01:22 +0000103static void
Damien Miller33804262001-02-04 23:20:18 +1100104send_string_request(int fd, u_int id, u_int code, char *s,
105 u_int len)
106{
107 Buffer msg;
108
109 buffer_init(&msg);
110 buffer_put_char(&msg, code);
111 buffer_put_int(&msg, id);
112 buffer_put_string(&msg, s, len);
113 send_msg(fd, &msg);
114 debug3("Sent message fd %d T:%d I:%d", fd, code, id);
115 buffer_free(&msg);
116}
117
Ben Lindstrombba81212001-06-25 05:01:22 +0000118static void
Damien Miller33804262001-02-04 23:20:18 +1100119send_string_attrs_request(int fd, u_int id, u_int code, char *s,
120 u_int len, Attrib *a)
121{
122 Buffer msg;
123
124 buffer_init(&msg);
125 buffer_put_char(&msg, code);
126 buffer_put_int(&msg, id);
127 buffer_put_string(&msg, s, len);
128 encode_attrib(&msg, a);
129 send_msg(fd, &msg);
130 debug3("Sent message fd %d T:%d I:%d", fd, code, id);
131 buffer_free(&msg);
132}
133
Ben Lindstrombba81212001-06-25 05:01:22 +0000134static u_int
Damien Miller33804262001-02-04 23:20:18 +1100135get_status(int fd, int expected_id)
136{
137 Buffer msg;
138 u_int type, id, status;
139
140 buffer_init(&msg);
141 get_msg(fd, &msg);
142 type = buffer_get_char(&msg);
143 id = buffer_get_int(&msg);
144
145 if (id != expected_id)
146 fatal("ID mismatch (%d != %d)", id, expected_id);
147 if (type != SSH2_FXP_STATUS)
148 fatal("Expected SSH2_FXP_STATUS(%d) packet, got %d",
149 SSH2_FXP_STATUS, type);
150
151 status = buffer_get_int(&msg);
152 buffer_free(&msg);
153
154 debug3("SSH2_FXP_STATUS %d", status);
155
156 return(status);
157}
158
Ben Lindstrombba81212001-06-25 05:01:22 +0000159static char *
Damien Miller33804262001-02-04 23:20:18 +1100160get_handle(int fd, u_int expected_id, u_int *len)
161{
162 Buffer msg;
163 u_int type, id;
164 char *handle;
165
166 buffer_init(&msg);
167 get_msg(fd, &msg);
168 type = buffer_get_char(&msg);
169 id = buffer_get_int(&msg);
170
171 if (id != expected_id)
172 fatal("ID mismatch (%d != %d)", id, expected_id);
173 if (type == SSH2_FXP_STATUS) {
174 int status = buffer_get_int(&msg);
175
176 error("Couldn't get handle: %s", fx2txt(status));
177 return(NULL);
178 } else if (type != SSH2_FXP_HANDLE)
179 fatal("Expected SSH2_FXP_HANDLE(%d) packet, got %d",
180 SSH2_FXP_HANDLE, type);
181
182 handle = buffer_get_string(&msg, len);
183 buffer_free(&msg);
184
185 return(handle);
186}
187
Ben Lindstrombba81212001-06-25 05:01:22 +0000188static Attrib *
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000189get_decode_stat(int fd, u_int expected_id, int quiet)
Damien Miller33804262001-02-04 23:20:18 +1100190{
191 Buffer msg;
192 u_int type, id;
193 Attrib *a;
194
195 buffer_init(&msg);
196 get_msg(fd, &msg);
197
198 type = buffer_get_char(&msg);
199 id = buffer_get_int(&msg);
200
201 debug3("Received stat reply T:%d I:%d", type, id);
202 if (id != expected_id)
203 fatal("ID mismatch (%d != %d)", id, expected_id);
204 if (type == SSH2_FXP_STATUS) {
205 int status = buffer_get_int(&msg);
206
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000207 if (quiet)
208 debug("Couldn't stat remote file: %s", fx2txt(status));
209 else
210 error("Couldn't stat remote file: %s", fx2txt(status));
Damien Miller33804262001-02-04 23:20:18 +1100211 return(NULL);
212 } else if (type != SSH2_FXP_ATTRS) {
213 fatal("Expected SSH2_FXP_ATTRS(%d) packet, got %d",
214 SSH2_FXP_ATTRS, type);
215 }
216 a = decode_attrib(&msg);
217 buffer_free(&msg);
218
219 return(a);
220}
221
222int
223do_init(int fd_in, int fd_out)
224{
225 int type, version;
226 Buffer msg;
227
228 buffer_init(&msg);
229 buffer_put_char(&msg, SSH2_FXP_INIT);
230 buffer_put_int(&msg, SSH2_FILEXFER_VERSION);
231 send_msg(fd_out, &msg);
232
233 buffer_clear(&msg);
234
235 get_msg(fd_in, &msg);
236
Kevin Stevesef4eea92001-02-05 12:42:17 +0000237 /* Expecting a VERSION reply */
Damien Miller33804262001-02-04 23:20:18 +1100238 if ((type = buffer_get_char(&msg)) != SSH2_FXP_VERSION) {
239 error("Invalid packet back from SSH2_FXP_INIT (type %d)",
240 type);
241 buffer_free(&msg);
242 return(-1);
243 }
244 version = buffer_get_int(&msg);
245
246 debug2("Remote version: %d", version);
247
248 /* Check for extensions */
249 while (buffer_len(&msg) > 0) {
250 char *name = buffer_get_string(&msg, NULL);
251 char *value = buffer_get_string(&msg, NULL);
252
253 debug2("Init extension: \"%s\"", name);
254 xfree(name);
255 xfree(value);
256 }
257
258 buffer_free(&msg);
Damien Miller058316f2001-03-08 10:08:49 +1100259
260 return(version);
Damien Miller33804262001-02-04 23:20:18 +1100261}
262
263int
264do_close(int fd_in, int fd_out, char *handle, u_int handle_len)
265{
266 u_int id, status;
267 Buffer msg;
268
269 buffer_init(&msg);
270
Ben Lindstrom288cc392001-02-09 02:58:04 +0000271 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100272 buffer_put_char(&msg, SSH2_FXP_CLOSE);
273 buffer_put_int(&msg, id);
274 buffer_put_string(&msg, handle, handle_len);
275 send_msg(fd_out, &msg);
276 debug3("Sent message SSH2_FXP_CLOSE I:%d", id);
277
278 status = get_status(fd_in, id);
279 if (status != SSH2_FX_OK)
280 error("Couldn't close file: %s", fx2txt(status));
281
282 buffer_free(&msg);
283
284 return(status);
285}
286
Damien Miller4870afd2001-03-14 10:27:09 +1100287
Ben Lindstrombba81212001-06-25 05:01:22 +0000288static int
Ben Lindstroma3700052001-04-05 23:26:32 +0000289do_lsreaddir(int fd_in, int fd_out, char *path, int printflag,
Damien Miller4870afd2001-03-14 10:27:09 +1100290 SFTP_DIRENT ***dir)
Damien Miller33804262001-02-04 23:20:18 +1100291{
292 Buffer msg;
Ben Lindstrom025df4a2001-03-14 15:16:34 +0000293 u_int type, id, handle_len, i, expected_id, ents = 0;
Damien Miller33804262001-02-04 23:20:18 +1100294 char *handle;
295
Ben Lindstrom288cc392001-02-09 02:58:04 +0000296 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100297
298 buffer_init(&msg);
299 buffer_put_char(&msg, SSH2_FXP_OPENDIR);
300 buffer_put_int(&msg, id);
301 buffer_put_cstring(&msg, path);
302 send_msg(fd_out, &msg);
303
304 buffer_clear(&msg);
305
306 handle = get_handle(fd_in, id, &handle_len);
307 if (handle == NULL)
308 return(-1);
309
Damien Miller4870afd2001-03-14 10:27:09 +1100310 if (dir) {
311 ents = 0;
312 *dir = xmalloc(sizeof(**dir));
313 (*dir)[0] = NULL;
314 }
Damien Miller4870afd2001-03-14 10:27:09 +1100315
Damien Miller9f0f5c62001-12-21 14:45:46 +1100316 for (;;) {
Damien Miller33804262001-02-04 23:20:18 +1100317 int count;
318
Ben Lindstrom288cc392001-02-09 02:58:04 +0000319 id = expected_id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100320
321 debug3("Sending SSH2_FXP_READDIR I:%d", id);
322
323 buffer_clear(&msg);
324 buffer_put_char(&msg, SSH2_FXP_READDIR);
325 buffer_put_int(&msg, id);
326 buffer_put_string(&msg, handle, handle_len);
327 send_msg(fd_out, &msg);
328
329 buffer_clear(&msg);
330
331 get_msg(fd_in, &msg);
332
333 type = buffer_get_char(&msg);
334 id = buffer_get_int(&msg);
335
336 debug3("Received reply T:%d I:%d", type, id);
337
338 if (id != expected_id)
339 fatal("ID mismatch (%d != %d)", id, expected_id);
340
341 if (type == SSH2_FXP_STATUS) {
342 int status = buffer_get_int(&msg);
343
344 debug3("Received SSH2_FXP_STATUS %d", status);
345
346 if (status == SSH2_FX_EOF) {
347 break;
348 } else {
349 error("Couldn't read directory: %s",
350 fx2txt(status));
351 do_close(fd_in, fd_out, handle, handle_len);
Ben Lindstrom10ac33f2001-02-10 21:53:40 +0000352 return(status);
Damien Miller33804262001-02-04 23:20:18 +1100353 }
354 } else if (type != SSH2_FXP_NAME)
355 fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
356 SSH2_FXP_NAME, type);
357
358 count = buffer_get_int(&msg);
Damien Millerd7686fd2001-02-10 00:40:03 +1100359 if (count == 0)
360 break;
361 debug3("Received %d SSH2_FXP_NAME responses", count);
Damien Miller9f0f5c62001-12-21 14:45:46 +1100362 for (i = 0; i < count; i++) {
Damien Miller33804262001-02-04 23:20:18 +1100363 char *filename, *longname;
364 Attrib *a;
365
366 filename = buffer_get_string(&msg, NULL);
367 longname = buffer_get_string(&msg, NULL);
368 a = decode_attrib(&msg);
369
Damien Miller4870afd2001-03-14 10:27:09 +1100370 if (printflag)
371 printf("%s\n", longname);
372
373 if (dir) {
Ben Lindstroma3700052001-04-05 23:26:32 +0000374 *dir = xrealloc(*dir, sizeof(**dir) *
Damien Miller4870afd2001-03-14 10:27:09 +1100375 (ents + 2));
376 (*dir)[ents] = xmalloc(sizeof(***dir));
377 (*dir)[ents]->filename = xstrdup(filename);
378 (*dir)[ents]->longname = xstrdup(longname);
379 memcpy(&(*dir)[ents]->a, a, sizeof(*a));
380 (*dir)[++ents] = NULL;
381 }
Damien Miller33804262001-02-04 23:20:18 +1100382
383 xfree(filename);
384 xfree(longname);
385 }
386 }
387
388 buffer_free(&msg);
389 do_close(fd_in, fd_out, handle, handle_len);
390 xfree(handle);
391
392 return(0);
393}
394
395int
Damien Miller4870afd2001-03-14 10:27:09 +1100396do_ls(int fd_in, int fd_out, char *path)
397{
398 return(do_lsreaddir(fd_in, fd_out, path, 1, NULL));
399}
400
401int
402do_readdir(int fd_in, int fd_out, char *path, SFTP_DIRENT ***dir)
403{
404 return(do_lsreaddir(fd_in, fd_out, path, 0, dir));
405}
406
407void free_sftp_dirents(SFTP_DIRENT **s)
408{
409 int i;
Damien Miller9f0f5c62001-12-21 14:45:46 +1100410
411 for (i = 0; s[i]; i++) {
Damien Miller4870afd2001-03-14 10:27:09 +1100412 xfree(s[i]->filename);
413 xfree(s[i]->longname);
414 xfree(s[i]);
415 }
416 xfree(s);
417}
418
419int
Damien Miller33804262001-02-04 23:20:18 +1100420do_rm(int fd_in, int fd_out, char *path)
421{
422 u_int status, id;
423
424 debug2("Sending SSH2_FXP_REMOVE \"%s\"", path);
425
Ben Lindstrom288cc392001-02-09 02:58:04 +0000426 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100427 send_string_request(fd_out, id, SSH2_FXP_REMOVE, path, strlen(path));
428 status = get_status(fd_in, id);
429 if (status != SSH2_FX_OK)
430 error("Couldn't delete file: %s", fx2txt(status));
431 return(status);
432}
433
434int
435do_mkdir(int fd_in, int fd_out, char *path, Attrib *a)
436{
437 u_int status, id;
438
Ben Lindstrom288cc392001-02-09 02:58:04 +0000439 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100440 send_string_attrs_request(fd_out, id, SSH2_FXP_MKDIR, path,
441 strlen(path), a);
442
443 status = get_status(fd_in, id);
444 if (status != SSH2_FX_OK)
445 error("Couldn't create directory: %s", fx2txt(status));
446
447 return(status);
448}
449
450int
451do_rmdir(int fd_in, int fd_out, char *path)
452{
453 u_int status, id;
454
Ben Lindstrom288cc392001-02-09 02:58:04 +0000455 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100456 send_string_request(fd_out, id, SSH2_FXP_RMDIR, path, strlen(path));
457
458 status = get_status(fd_in, id);
459 if (status != SSH2_FX_OK)
460 error("Couldn't remove directory: %s", fx2txt(status));
461
462 return(status);
463}
464
465Attrib *
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000466do_stat(int fd_in, int fd_out, char *path, int quiet)
Damien Miller33804262001-02-04 23:20:18 +1100467{
468 u_int id;
469
Ben Lindstrom288cc392001-02-09 02:58:04 +0000470 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100471 send_string_request(fd_out, id, SSH2_FXP_STAT, path, strlen(path));
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000472 return(get_decode_stat(fd_in, id, quiet));
Damien Miller33804262001-02-04 23:20:18 +1100473}
474
475Attrib *
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000476do_lstat(int fd_in, int fd_out, char *path, int quiet)
Damien Miller33804262001-02-04 23:20:18 +1100477{
478 u_int id;
479
Ben Lindstrom288cc392001-02-09 02:58:04 +0000480 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100481 send_string_request(fd_out, id, SSH2_FXP_LSTAT, path, strlen(path));
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000482 return(get_decode_stat(fd_in, id, quiet));
Damien Miller33804262001-02-04 23:20:18 +1100483}
484
485Attrib *
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000486do_fstat(int fd_in, int fd_out, char *handle, u_int handle_len, int quiet)
Damien Miller33804262001-02-04 23:20:18 +1100487{
488 u_int id;
489
Ben Lindstrom288cc392001-02-09 02:58:04 +0000490 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100491 send_string_request(fd_out, id, SSH2_FXP_FSTAT, handle, handle_len);
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000492 return(get_decode_stat(fd_in, id, quiet));
Damien Miller33804262001-02-04 23:20:18 +1100493}
494
495int
496do_setstat(int fd_in, int fd_out, char *path, Attrib *a)
497{
498 u_int status, id;
499
Ben Lindstrom288cc392001-02-09 02:58:04 +0000500 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100501 send_string_attrs_request(fd_out, id, SSH2_FXP_SETSTAT, path,
502 strlen(path), a);
503
504 status = get_status(fd_in, id);
505 if (status != SSH2_FX_OK)
506 error("Couldn't setstat on \"%s\": %s", path,
507 fx2txt(status));
508
509 return(status);
510}
511
512int
513do_fsetstat(int fd_in, int fd_out, char *handle, u_int handle_len,
514 Attrib *a)
515{
516 u_int status, id;
517
Ben Lindstrom288cc392001-02-09 02:58:04 +0000518 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100519 send_string_attrs_request(fd_out, id, SSH2_FXP_FSETSTAT, handle,
520 handle_len, a);
521
522 status = get_status(fd_in, id);
523 if (status != SSH2_FX_OK)
524 error("Couldn't fsetstat: %s", fx2txt(status));
525
526 return(status);
527}
528
529char *
530do_realpath(int fd_in, int fd_out, char *path)
531{
532 Buffer msg;
533 u_int type, expected_id, count, id;
534 char *filename, *longname;
535 Attrib *a;
536
Ben Lindstrom288cc392001-02-09 02:58:04 +0000537 expected_id = id = msg_id++;
Damien Miller058316f2001-03-08 10:08:49 +1100538 send_string_request(fd_out, id, SSH2_FXP_REALPATH, path, strlen(path));
Damien Miller33804262001-02-04 23:20:18 +1100539
540 buffer_init(&msg);
541
542 get_msg(fd_in, &msg);
543 type = buffer_get_char(&msg);
544 id = buffer_get_int(&msg);
545
546 if (id != expected_id)
547 fatal("ID mismatch (%d != %d)", id, expected_id);
548
549 if (type == SSH2_FXP_STATUS) {
550 u_int status = buffer_get_int(&msg);
551
552 error("Couldn't canonicalise: %s", fx2txt(status));
553 return(NULL);
554 } else if (type != SSH2_FXP_NAME)
555 fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
556 SSH2_FXP_NAME, type);
557
558 count = buffer_get_int(&msg);
559 if (count != 1)
560 fatal("Got multiple names (%d) from SSH_FXP_REALPATH", count);
561
562 filename = buffer_get_string(&msg, NULL);
563 longname = buffer_get_string(&msg, NULL);
564 a = decode_attrib(&msg);
565
566 debug3("SSH_FXP_REALPATH %s -> %s", path, filename);
567
568 xfree(longname);
569
570 buffer_free(&msg);
571
572 return(filename);
573}
574
575int
576do_rename(int fd_in, int fd_out, char *oldpath, char *newpath)
577{
578 Buffer msg;
579 u_int status, id;
580
581 buffer_init(&msg);
582
583 /* Send rename request */
Ben Lindstrom288cc392001-02-09 02:58:04 +0000584 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100585 buffer_put_char(&msg, SSH2_FXP_RENAME);
586 buffer_put_int(&msg, id);
587 buffer_put_cstring(&msg, oldpath);
588 buffer_put_cstring(&msg, newpath);
589 send_msg(fd_out, &msg);
590 debug3("Sent message SSH2_FXP_RENAME \"%s\" -> \"%s\"", oldpath,
591 newpath);
592 buffer_free(&msg);
593
594 status = get_status(fd_in, id);
595 if (status != SSH2_FX_OK)
596 error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath,
597 fx2txt(status));
598
599 return(status);
600}
601
602int
Damien Miller058316f2001-03-08 10:08:49 +1100603do_symlink(int fd_in, int fd_out, char *oldpath, char *newpath)
604{
605 Buffer msg;
606 u_int status, id;
607
608 buffer_init(&msg);
609
610 /* Send rename request */
611 id = msg_id++;
612 buffer_put_char(&msg, SSH2_FXP_SYMLINK);
613 buffer_put_int(&msg, id);
614 buffer_put_cstring(&msg, oldpath);
615 buffer_put_cstring(&msg, newpath);
616 send_msg(fd_out, &msg);
617 debug3("Sent message SSH2_FXP_SYMLINK \"%s\" -> \"%s\"", oldpath,
618 newpath);
619 buffer_free(&msg);
620
621 status = get_status(fd_in, id);
622 if (status != SSH2_FX_OK)
623 error("Couldn't rename file \"%s\" to \"%s\": %s", oldpath, newpath,
624 fx2txt(status));
625
626 return(status);
627}
628
629char *
630do_readlink(int fd_in, int fd_out, char *path)
631{
632 Buffer msg;
633 u_int type, expected_id, count, id;
634 char *filename, *longname;
635 Attrib *a;
636
637 expected_id = id = msg_id++;
638 send_string_request(fd_out, id, SSH2_FXP_READLINK, path, strlen(path));
639
640 buffer_init(&msg);
641
642 get_msg(fd_in, &msg);
643 type = buffer_get_char(&msg);
644 id = buffer_get_int(&msg);
645
646 if (id != expected_id)
647 fatal("ID mismatch (%d != %d)", id, expected_id);
648
649 if (type == SSH2_FXP_STATUS) {
650 u_int status = buffer_get_int(&msg);
651
652 error("Couldn't readlink: %s", fx2txt(status));
653 return(NULL);
654 } else if (type != SSH2_FXP_NAME)
655 fatal("Expected SSH2_FXP_NAME(%d) packet, got %d",
656 SSH2_FXP_NAME, type);
657
658 count = buffer_get_int(&msg);
659 if (count != 1)
660 fatal("Got multiple names (%d) from SSH_FXP_READLINK", count);
661
662 filename = buffer_get_string(&msg, NULL);
663 longname = buffer_get_string(&msg, NULL);
664 a = decode_attrib(&msg);
665
666 debug3("SSH_FXP_READLINK %s -> %s", path, filename);
667
668 xfree(longname);
669
670 buffer_free(&msg);
671
672 return(filename);
673}
674
Damien Miller16a13332002-02-13 14:03:56 +1100675static void
676send_read_request(int fd_out, u_int id, u_int64_t offset, u_int len,
677 char *handle, u_int handle_len)
678{
679 Buffer msg;
680
681 buffer_init(&msg);
682 buffer_clear(&msg);
683 buffer_put_char(&msg, SSH2_FXP_READ);
684 buffer_put_int(&msg, id);
685 buffer_put_string(&msg, handle, handle_len);
686 buffer_put_int64(&msg, offset);
687 buffer_put_int(&msg, len);
688 send_msg(fd_out, &msg);
689 buffer_free(&msg);
690}
691
Damien Miller058316f2001-03-08 10:08:49 +1100692int
Damien Miller33804262001-02-04 23:20:18 +1100693do_download(int fd_in, int fd_out, char *remote_path, char *local_path,
Damien Miller16a13332002-02-13 14:03:56 +1100694 int pflag, size_t buflen, int num_requests)
Damien Miller33804262001-02-04 23:20:18 +1100695{
Damien Miller33804262001-02-04 23:20:18 +1100696 Attrib junk, *a;
Damien Miller16a13332002-02-13 14:03:56 +1100697 Buffer msg;
698 char *handle;
699 int local_fd, status, num_req, max_req, write_error;
700 int read_error, write_errno;
701 u_int64_t offset, size;
702 u_int handle_len, mode, type, id;
703 struct request {
704 u_int id;
705 u_int len;
706 u_int64_t offset;
707 TAILQ_ENTRY(request) tq;
708 };
709 TAILQ_HEAD(reqhead, request) requests;
710 struct request *req;
711
712 TAILQ_INIT(&requests);
Damien Miller33804262001-02-04 23:20:18 +1100713
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000714 a = do_stat(fd_in, fd_out, remote_path, 0);
Damien Miller33804262001-02-04 23:20:18 +1100715 if (a == NULL)
716 return(-1);
717
718 /* XXX: should we preserve set[ug]id? */
719 if (a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS)
720 mode = S_IWRITE | (a->perm & 0777);
721 else
722 mode = 0666;
723
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000724 if ((a->flags & SSH2_FILEXFER_ATTR_PERMISSIONS) &&
725 (a->perm & S_IFDIR)) {
726 error("Cannot download a directory: %s", remote_path);
727 return(-1);
728 }
729
Damien Miller16a13332002-02-13 14:03:56 +1100730 if (a->flags & SSH2_FILEXFER_ATTR_SIZE)
731 size = a->size;
732 else
733 size = 0;
734
Damien Miller33804262001-02-04 23:20:18 +1100735 local_fd = open(local_path, O_WRONLY | O_CREAT | O_TRUNC, mode);
736 if (local_fd == -1) {
737 error("Couldn't open local file \"%s\" for writing: %s",
738 local_path, strerror(errno));
Ben Lindstromc8d1c302001-03-17 00:34:46 +0000739 return(-1);
Damien Miller33804262001-02-04 23:20:18 +1100740 }
741
Damien Miller33804262001-02-04 23:20:18 +1100742 buffer_init(&msg);
743
744 /* Send open request */
Ben Lindstrom288cc392001-02-09 02:58:04 +0000745 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100746 buffer_put_char(&msg, SSH2_FXP_OPEN);
747 buffer_put_int(&msg, id);
748 buffer_put_cstring(&msg, remote_path);
749 buffer_put_int(&msg, SSH2_FXF_READ);
750 attrib_clear(&junk); /* Send empty attributes */
751 encode_attrib(&msg, &junk);
752 send_msg(fd_out, &msg);
753 debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
754
755 handle = get_handle(fd_in, id, &handle_len);
756 if (handle == NULL) {
757 buffer_free(&msg);
758 close(local_fd);
759 return(-1);
760 }
761
762 /* Read from remote and write to local */
Damien Miller16a13332002-02-13 14:03:56 +1100763 write_error = read_error = write_errno = num_req = offset = 0;
764 max_req = 1;
765 while (num_req > 0 || max_req > 0) {
Damien Miller33804262001-02-04 23:20:18 +1100766 char *data;
Damien Miller16a13332002-02-13 14:03:56 +1100767 u_int len;
Damien Miller33804262001-02-04 23:20:18 +1100768
Damien Miller16a13332002-02-13 14:03:56 +1100769 /* Send some more requests */
770 while (num_req < max_req) {
771 debug3("Request range %llu -> %llu (%d/%d)",
772 offset, offset + buflen - 1, num_req, max_req);
773 req = xmalloc(sizeof(*req));
774 req->id = msg_id++;
775 req->len = buflen;
776 req->offset = offset;
777 offset += buflen;
778 num_req++;
779 TAILQ_INSERT_TAIL(&requests, req, tq);
780 send_read_request(fd_out, req->id, req->offset,
781 req->len, handle, handle_len);
782 }
Damien Miller33804262001-02-04 23:20:18 +1100783
784 buffer_clear(&msg);
Damien Miller33804262001-02-04 23:20:18 +1100785 get_msg(fd_in, &msg);
786 type = buffer_get_char(&msg);
787 id = buffer_get_int(&msg);
Damien Miller16a13332002-02-13 14:03:56 +1100788 debug3("Received reply T:%d I:%d R:%d", type, id, max_req);
Damien Miller33804262001-02-04 23:20:18 +1100789
Damien Miller16a13332002-02-13 14:03:56 +1100790 /* Find the request in our queue */
791 for(req = TAILQ_FIRST(&requests);
792 req != NULL && req->id != id;
793 req = TAILQ_NEXT(req, tq))
794 ;
795 if (req == NULL)
796 fatal("Unexpected reply %u", id);
797
798 switch (type) {
799 case SSH2_FXP_STATUS:
800 status = buffer_get_int(&msg);
801 if (status != SSH2_FX_EOF)
802 read_error = 1;
803 max_req = 0;
804 TAILQ_REMOVE(&requests, req, tq);
805 xfree(req);
806 num_req--;
807 break;
808 case SSH2_FXP_DATA:
809 data = buffer_get_string(&msg, &len);
810 debug3("Received data %llu -> %llu", req->offset,
811 req->offset + len - 1);
812 if (len > req->len)
813 fatal("Received more data than asked for "
814 "%d > %d", len, req->len);
815 if ((lseek(local_fd, req->offset, SEEK_SET) == -1 ||
816 atomicio(write, local_fd, data, len) != len) &&
817 !write_error) {
818 write_errno = errno;
819 write_error = 1;
820 max_req = 0;
Damien Miller33804262001-02-04 23:20:18 +1100821 }
Damien Miller16a13332002-02-13 14:03:56 +1100822 xfree(data);
823
824 if (len == req->len) {
825 TAILQ_REMOVE(&requests, req, tq);
826 xfree(req);
827 num_req--;
828 } else {
829 /* Resend the request for the missing data */
830 debug3("Short data block, re-requesting "
831 "%llu -> %llu (%2d)", req->offset + len,
832 req->offset + req->len - 1, num_req);
833 req->id = msg_id++;
834 req->len -= len;
835 req->offset += len;
836 send_read_request(fd_out, req->id,
837 req->offset, req->len, handle,
838 handle_len);
839 /* Reduce the request size */
840 if (len < buflen)
841 buflen = MAX(MIN_READ_SIZE, len);
842 }
843 if (max_req > 0) { /* max_req = 0 iff EOF received */
844 if (size > 0 && offset > size) {
845 /* Only one request at a time
846 * after the expected EOF */
847 debug3("Finish at %llu (%2d)",
848 offset, num_req);
849 max_req = 1;
850 }
851 else if (max_req < num_requests + 1) {
852 ++max_req;
853 }
854 }
855 break;
856 default:
Damien Miller33804262001-02-04 23:20:18 +1100857 fatal("Expected SSH2_FXP_DATA(%d) packet, got %d",
858 SSH2_FXP_DATA, type);
859 }
Damien Miller33804262001-02-04 23:20:18 +1100860 }
Damien Miller33804262001-02-04 23:20:18 +1100861
Damien Miller16a13332002-02-13 14:03:56 +1100862 /* Sanity check */
863 if (TAILQ_FIRST(&requests) != NULL)
864 fatal("Transfer complete, but requests still in queue");
865
866 if (read_error) {
867 error("Couldn't read from remote "
868 "file \"%s\" : %s", remote_path,
869 fx2txt(status));
870 do_close(fd_in, fd_out, handle, handle_len);
871 } else if (write_error) {
872 error("Couldn't write to \"%s\": %s", local_path,
873 strerror(write_errno));
874 status = -1;
875 do_close(fd_in, fd_out, handle, handle_len);
876 } else {
877 status = do_close(fd_in, fd_out, handle, handle_len);
878
879 /* Override umask and utimes if asked */
Ben Lindstrom6dc75f52001-02-17 16:47:47 +0000880#ifdef HAVE_FCHMOD
Damien Miller16a13332002-02-13 14:03:56 +1100881 if (pflag && fchmod(local_fd, mode) == -1)
Ben Lindstrom6dc75f52001-02-17 16:47:47 +0000882#else
Damien Miller16a13332002-02-13 14:03:56 +1100883 if (pflag && chmod(local_path, mode) == -1)
Ben Lindstrom6dc75f52001-02-17 16:47:47 +0000884#endif /* HAVE_FCHMOD */
Damien Miller16a13332002-02-13 14:03:56 +1100885 error("Couldn't set mode on \"%s\": %s", local_path,
886 strerror(errno));
887 if (pflag && (a->flags & SSH2_FILEXFER_ATTR_ACMODTIME)) {
888 struct timeval tv[2];
889 tv[0].tv_sec = a->atime;
890 tv[1].tv_sec = a->mtime;
891 tv[0].tv_usec = tv[1].tv_usec = 0;
892 if (utimes(local_path, tv) == -1)
893 error("Can't set times on \"%s\": %s",
894 local_path, strerror(errno));
895 }
Ben Lindstrom9d4f2c82001-02-15 03:22:45 +0000896 }
Damien Millerd7686fd2001-02-10 00:40:03 +1100897 close(local_fd);
898 buffer_free(&msg);
899 xfree(handle);
900 return status;
Damien Miller33804262001-02-04 23:20:18 +1100901}
902
903int
904do_upload(int fd_in, int fd_out, char *local_path, char *remote_path,
Damien Miller16a13332002-02-13 14:03:56 +1100905 int pflag, size_t buflen, int num_requests)
Damien Miller33804262001-02-04 23:20:18 +1100906{
Damien Miller8829d362002-02-08 22:04:05 +1100907 int local_fd, status;
Damien Miller33804262001-02-04 23:20:18 +1100908 u_int handle_len, id;
909 u_int64_t offset;
Damien Miller8829d362002-02-08 22:04:05 +1100910 char *handle, *data;
Damien Miller33804262001-02-04 23:20:18 +1100911 Buffer msg;
912 struct stat sb;
913 Attrib a;
Damien Miller16a13332002-02-13 14:03:56 +1100914 u_int32_t startid;
915 u_int32_t ackid;
Damien Miller33804262001-02-04 23:20:18 +1100916
917 if ((local_fd = open(local_path, O_RDONLY, 0)) == -1) {
918 error("Couldn't open local file \"%s\" for reading: %s",
919 local_path, strerror(errno));
920 return(-1);
921 }
922 if (fstat(local_fd, &sb) == -1) {
923 error("Couldn't fstat local file \"%s\": %s",
924 local_path, strerror(errno));
925 close(local_fd);
926 return(-1);
927 }
928 stat_to_attrib(&sb, &a);
929
930 a.flags &= ~SSH2_FILEXFER_ATTR_SIZE;
931 a.flags &= ~SSH2_FILEXFER_ATTR_UIDGID;
932 a.perm &= 0777;
933 if (!pflag)
934 a.flags &= ~SSH2_FILEXFER_ATTR_ACMODTIME;
935
936 buffer_init(&msg);
937
938 /* Send open request */
Ben Lindstrom288cc392001-02-09 02:58:04 +0000939 id = msg_id++;
Damien Miller33804262001-02-04 23:20:18 +1100940 buffer_put_char(&msg, SSH2_FXP_OPEN);
941 buffer_put_int(&msg, id);
942 buffer_put_cstring(&msg, remote_path);
943 buffer_put_int(&msg, SSH2_FXF_WRITE|SSH2_FXF_CREAT|SSH2_FXF_TRUNC);
944 encode_attrib(&msg, &a);
945 send_msg(fd_out, &msg);
946 debug3("Sent message SSH2_FXP_OPEN I:%d P:%s", id, remote_path);
947
948 buffer_clear(&msg);
949
950 handle = get_handle(fd_in, id, &handle_len);
951 if (handle == NULL) {
952 close(local_fd);
953 buffer_free(&msg);
954 return(-1);
955 }
956
Damien Miller16a13332002-02-13 14:03:56 +1100957 startid = ackid = id + 1;
Damien Miller8829d362002-02-08 22:04:05 +1100958 data = xmalloc(buflen);
959
Damien Miller33804262001-02-04 23:20:18 +1100960 /* Read from local and write to remote */
961 offset = 0;
Damien Miller9f0f5c62001-12-21 14:45:46 +1100962 for (;;) {
Damien Miller33804262001-02-04 23:20:18 +1100963 int len;
Damien Miller33804262001-02-04 23:20:18 +1100964
965 /*
966 * Can't use atomicio here because it returns 0 on EOF, thus losing
967 * the last block of the file
968 */
969 do
Damien Miller8829d362002-02-08 22:04:05 +1100970 len = read(local_fd, data, buflen);
Damien Miller33804262001-02-04 23:20:18 +1100971 while ((len == -1) && (errno == EINTR || errno == EAGAIN));
972
973 if (len == -1)
974 fatal("Couldn't read from \"%s\": %s", local_path,
975 strerror(errno));
Damien Miller16a13332002-02-13 14:03:56 +1100976
977 if (len != 0) {
978 buffer_clear(&msg);
979 buffer_put_char(&msg, SSH2_FXP_WRITE);
980 buffer_put_int(&msg, ++id);
981 buffer_put_string(&msg, handle, handle_len);
982 buffer_put_int64(&msg, offset);
983 buffer_put_string(&msg, data, len);
984 send_msg(fd_out, &msg);
985 debug3("Sent message SSH2_FXP_WRITE I:%d O:%llu S:%u",
986 id, (u_int64_t)offset, len);
987 } else if ( id < ackid )
Damien Miller33804262001-02-04 23:20:18 +1100988 break;
989
Damien Miller16a13332002-02-13 14:03:56 +1100990 if (id == startid || len == 0 ||
991 id - ackid >= num_requests) {
992 status = get_status(fd_in, ackid);
993 if (status != SSH2_FX_OK) {
994 error("Couldn't write to remote file \"%s\": %s",
995 remote_path, fx2txt(status));
996 do_close(fd_in, fd_out, handle, handle_len);
997 close(local_fd);
998 goto done;
999 }
1000 debug3("In write loop, got %d offset %llu", len,
1001 (u_int64_t)offset);
1002 ++ackid;
Damien Miller33804262001-02-04 23:20:18 +11001003 }
Damien Miller33804262001-02-04 23:20:18 +11001004
1005 offset += len;
1006 }
Damien Miller8829d362002-02-08 22:04:05 +11001007 xfree(data);
Damien Miller33804262001-02-04 23:20:18 +11001008
1009 if (close(local_fd) == -1) {
1010 error("Couldn't close local file \"%s\": %s", local_path,
1011 strerror(errno));
1012 do_close(fd_in, fd_out, handle, handle_len);
Damien Millerd7686fd2001-02-10 00:40:03 +11001013 status = -1;
1014 goto done;
Damien Miller33804262001-02-04 23:20:18 +11001015 }
1016
Ben Lindstrom9d4f2c82001-02-15 03:22:45 +00001017 /* Override umask and utimes if asked */
1018 if (pflag)
1019 do_fsetstat(fd_in, fd_out, handle, handle_len, &a);
1020
Damien Millerd7686fd2001-02-10 00:40:03 +11001021 status = do_close(fd_in, fd_out, handle, handle_len);
1022
1023done:
1024 xfree(handle);
1025 buffer_free(&msg);
1026 return status;
Damien Miller33804262001-02-04 23:20:18 +11001027}