Mention PLINK_PROTOCOL in the Plink chapter.
[u/mdw/putty] / sftp.c
CommitLineData
4c7f0d61 1/*
2 * sftp.c: SFTP generic client code.
3 */
4
5#include <stdio.h>
6#include <stdlib.h>
4a8fc3c4 7#include <string.h>
4c7f0d61 8#include <assert.h>
4c7f0d61 9
10#include "int64.h"
11#include "sftp.h"
12
13#define smalloc malloc
14#define srealloc realloc
15#define sfree free
16
17#define GET_32BIT(cp) \
18 (((unsigned long)(unsigned char)(cp)[0] << 24) | \
19 ((unsigned long)(unsigned char)(cp)[1] << 16) | \
20 ((unsigned long)(unsigned char)(cp)[2] << 8) | \
21 ((unsigned long)(unsigned char)(cp)[3]))
22
23#define PUT_32BIT(cp, value) { \
24 (cp)[0] = (unsigned char)((value) >> 24); \
25 (cp)[1] = (unsigned char)((value) >> 16); \
26 (cp)[2] = (unsigned char)((value) >> 8); \
27 (cp)[3] = (unsigned char)(value); }
28
29struct sftp_packet {
30 char *data;
31 int length, maxlen;
32 int savedpos;
33 int type;
34};
35
36/* ----------------------------------------------------------------------
37 * SFTP packet construction functions.
38 */
32874aea 39static void sftp_pkt_ensure(struct sftp_packet *pkt, int length)
40{
4c7f0d61 41 if (pkt->maxlen < length) {
32874aea 42 pkt->maxlen = length + 256;
4c7f0d61 43 pkt->data = srealloc(pkt->data, pkt->maxlen);
44 }
45}
32874aea 46static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len)
47{
4c7f0d61 48 pkt->length += len;
49 sftp_pkt_ensure(pkt, pkt->length);
32874aea 50 memcpy(pkt->data + pkt->length - len, data, len);
4c7f0d61 51}
32874aea 52static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte)
53{
4c7f0d61 54 sftp_pkt_adddata(pkt, &byte, 1);
55}
32874aea 56static struct sftp_packet *sftp_pkt_init(int pkt_type)
57{
4c7f0d61 58 struct sftp_packet *pkt;
59 pkt = smalloc(sizeof(struct sftp_packet));
60 pkt->data = NULL;
61 pkt->savedpos = -1;
62 pkt->length = 0;
63 pkt->maxlen = 0;
32874aea 64 sftp_pkt_addbyte(pkt, (unsigned char) pkt_type);
4c7f0d61 65 return pkt;
66}
32874aea 67static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value)
68{
4c7f0d61 69 sftp_pkt_adddata(pkt, &value, 1);
70}
32874aea 71static void sftp_pkt_adduint32(struct sftp_packet *pkt,
72 unsigned long value)
73{
4c7f0d61 74 unsigned char x[4];
75 PUT_32BIT(x, value);
76 sftp_pkt_adddata(pkt, x, 4);
77}
32874aea 78static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value)
79{
4c7f0d61 80 unsigned char x[8];
81 PUT_32BIT(x, value.hi);
32874aea 82 PUT_32BIT(x + 4, value.lo);
4c7f0d61 83 sftp_pkt_adddata(pkt, x, 8);
84}
32874aea 85static void sftp_pkt_addstring_start(struct sftp_packet *pkt)
86{
4c7f0d61 87 sftp_pkt_adduint32(pkt, 0);
88 pkt->savedpos = pkt->length;
89}
32874aea 90static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data)
91{
4c7f0d61 92 sftp_pkt_adddata(pkt, data, strlen(data));
32874aea 93 PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
4c7f0d61 94}
95static void sftp_pkt_addstring_data(struct sftp_packet *pkt,
32874aea 96 char *data, int len)
97{
4c7f0d61 98 sftp_pkt_adddata(pkt, data, len);
32874aea 99 PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
4c7f0d61 100}
32874aea 101static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data)
102{
4c7f0d61 103 sftp_pkt_addstring_start(pkt);
104 sftp_pkt_addstring_str(pkt, data);
105}
106
107/* ----------------------------------------------------------------------
108 * SFTP packet decode functions.
109 */
110
32874aea 111static unsigned char sftp_pkt_getbyte(struct sftp_packet *pkt)
112{
d8770b12 113 unsigned char value;
4c7f0d61 114 if (pkt->length - pkt->savedpos < 1)
32874aea 115 return 0; /* arrgh, no way to decline (FIXME?) */
4c7f0d61 116 value = (unsigned char) pkt->data[pkt->savedpos];
117 pkt->savedpos++;
118 return value;
119}
32874aea 120static unsigned long sftp_pkt_getuint32(struct sftp_packet *pkt)
121{
4c7f0d61 122 unsigned long value;
123 if (pkt->length - pkt->savedpos < 4)
32874aea 124 return 0; /* arrgh, no way to decline (FIXME?) */
125 value = GET_32BIT(pkt->data + pkt->savedpos);
4c7f0d61 126 pkt->savedpos += 4;
127 return value;
128}
129static void sftp_pkt_getstring(struct sftp_packet *pkt,
32874aea 130 char **p, int *length)
131{
4c7f0d61 132 *p = NULL;
133 if (pkt->length - pkt->savedpos < 4)
32874aea 134 return;
135 *length = GET_32BIT(pkt->data + pkt->savedpos);
4c7f0d61 136 pkt->savedpos += 4;
137 if (pkt->length - pkt->savedpos < *length)
32874aea 138 return;
139 *p = pkt->data + pkt->savedpos;
4c7f0d61 140 pkt->savedpos += *length;
141}
32874aea 142static struct fxp_attrs sftp_pkt_getattrs(struct sftp_packet *pkt)
143{
4c7f0d61 144 struct fxp_attrs ret;
145 ret.flags = sftp_pkt_getuint32(pkt);
146 if (ret.flags & SSH_FILEXFER_ATTR_SIZE) {
147 unsigned long hi, lo;
148 hi = sftp_pkt_getuint32(pkt);
149 lo = sftp_pkt_getuint32(pkt);
150 ret.size = uint64_make(hi, lo);
151 }
152 if (ret.flags & SSH_FILEXFER_ATTR_UIDGID) {
153 ret.uid = sftp_pkt_getuint32(pkt);
154 ret.gid = sftp_pkt_getuint32(pkt);
155 }
156 if (ret.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
157 ret.permissions = sftp_pkt_getuint32(pkt);
158 }
159 if (ret.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
160 ret.atime = sftp_pkt_getuint32(pkt);
161 ret.mtime = sftp_pkt_getuint32(pkt);
162 }
163 if (ret.flags & SSH_FILEXFER_ATTR_EXTENDED) {
164 int count;
165 count = sftp_pkt_getuint32(pkt);
166 while (count--) {
167 char *str;
168 int len;
169 /*
170 * We should try to analyse these, if we ever find one
171 * we recognise.
172 */
173 sftp_pkt_getstring(pkt, &str, &len);
174 sftp_pkt_getstring(pkt, &str, &len);
175 }
176 }
177 return ret;
178}
32874aea 179static void sftp_pkt_free(struct sftp_packet *pkt)
180{
181 if (pkt->data)
182 sfree(pkt->data);
4c7f0d61 183 sfree(pkt);
184}
185
186/* ----------------------------------------------------------------------
4a8fc3c4 187 * Send and receive packet functions.
4c7f0d61 188 */
32874aea 189int sftp_send(struct sftp_packet *pkt)
190{
4a8fc3c4 191 int ret;
4c7f0d61 192 char x[4];
193 PUT_32BIT(x, pkt->length);
32874aea 194 ret = (sftp_senddata(x, 4) && sftp_senddata(pkt->data, pkt->length));
4c7f0d61 195 sftp_pkt_free(pkt);
4a8fc3c4 196 return ret;
4c7f0d61 197}
32874aea 198struct sftp_packet *sftp_recv(void)
199{
4c7f0d61 200 struct sftp_packet *pkt;
201 char x[4];
4c7f0d61 202
4a8fc3c4 203 if (!sftp_recvdata(x, 4))
204 return NULL;
4c7f0d61 205
206 pkt = smalloc(sizeof(struct sftp_packet));
207 pkt->savedpos = 0;
208 pkt->length = pkt->maxlen = GET_32BIT(x);
209 pkt->data = smalloc(pkt->length);
210
4a8fc3c4 211 if (!sftp_recvdata(pkt->data, pkt->length)) {
212 sftp_pkt_free(pkt);
213 return NULL;
4c7f0d61 214 }
215
216 pkt->type = sftp_pkt_getbyte(pkt);
217
218 return pkt;
219}
220
221/* ----------------------------------------------------------------------
222 * String handling routines.
223 */
224
32874aea 225static char *mkstr(char *s, int len)
226{
227 char *p = smalloc(len + 1);
4c7f0d61 228 memcpy(p, s, len);
229 p[len] = '\0';
230 return p;
231}
232
233/* ----------------------------------------------------------------------
234 * SFTP primitives.
235 */
236
237static const char *fxp_error_message;
238static int fxp_errtype;
239
240/*
241 * Deal with (and free) an FXP_STATUS packet. Return 1 if
242 * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
243 * Also place the status into fxp_errtype.
244 */
32874aea 245static int fxp_got_status(struct sftp_packet *pktin)
246{
4c7f0d61 247 static const char *const messages[] = {
248 /* SSH_FX_OK. The only time we will display a _message_ for this
249 * is if we were expecting something other than FXP_STATUS on
250 * success, so this is actually an error message! */
251 "unexpected OK response",
252 "end of file",
253 "no such file or directory",
254 "permission denied",
255 "failure",
256 "bad message",
257 "no connection",
258 "connection lost",
259 "operation unsupported",
260 };
261
262 if (pktin->type != SSH_FXP_STATUS) {
263 fxp_error_message = "expected FXP_STATUS packet";
264 fxp_errtype = -1;
265 } else {
266 fxp_errtype = sftp_pkt_getuint32(pktin);
267 if (fxp_errtype < 0 ||
32874aea 268 fxp_errtype >= sizeof(messages) / sizeof(*messages))
269 fxp_error_message = "unknown error code";
4c7f0d61 270 else
271 fxp_error_message = messages[fxp_errtype];
272 }
273
274 if (fxp_errtype == SSH_FX_OK)
275 return 1;
276 else if (fxp_errtype == SSH_FX_EOF)
277 return 0;
278 else
279 return -1;
280}
281
32874aea 282static void fxp_internal_error(char *msg)
283{
4c7f0d61 284 fxp_error_message = msg;
285 fxp_errtype = -1;
286}
287
32874aea 288const char *fxp_error(void)
289{
4c7f0d61 290 return fxp_error_message;
291}
292
32874aea 293int fxp_error_type(void)
294{
4c7f0d61 295 return fxp_errtype;
296}
297
298/*
299 * Perform exchange of init/version packets. Return 0 on failure.
300 */
32874aea 301int fxp_init(void)
302{
4c7f0d61 303 struct sftp_packet *pktout, *pktin;
304 int remotever;
305
306 pktout = sftp_pkt_init(SSH_FXP_INIT);
307 sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
308 sftp_send(pktout);
309
310 pktin = sftp_recv();
0d694692 311 if (!pktin) {
312 fxp_internal_error("could not connect");
313 return 0;
314 }
4c7f0d61 315 if (pktin->type != SSH_FXP_VERSION) {
316 fxp_internal_error("did not receive FXP_VERSION");
317 return 0;
318 }
319 remotever = sftp_pkt_getuint32(pktin);
320 if (remotever > SFTP_PROTO_VERSION) {
32874aea 321 fxp_internal_error
322 ("remote protocol is more advanced than we support");
4c7f0d61 323 return 0;
324 }
325 /*
326 * In principle, this packet might also contain extension-
327 * string pairs. We should work through them and look for any
328 * we recognise. In practice we don't currently do so because
329 * we know we don't recognise _any_.
330 */
331 sftp_pkt_free(pktin);
332
333 return 1;
334}
335
336/*
f9e162aa 337 * Canonify a pathname.
4c7f0d61 338 */
32874aea 339char *fxp_realpath(char *path)
340{
4c7f0d61 341 struct sftp_packet *pktin, *pktout;
342 int id;
343
344 pktout = sftp_pkt_init(SSH_FXP_REALPATH);
345 sftp_pkt_adduint32(pktout, 0x123); /* request id */
346 sftp_pkt_addstring_start(pktout);
347 sftp_pkt_addstring_str(pktout, path);
4c7f0d61 348 sftp_send(pktout);
349 pktin = sftp_recv();
350 id = sftp_pkt_getuint32(pktin);
351 if (id != 0x123) {
352 fxp_internal_error("request ID mismatch\n");
353 return NULL;
354 }
355 if (pktin->type == SSH_FXP_NAME) {
356 int count;
357 char *path;
358 int len;
359
360 count = sftp_pkt_getuint32(pktin);
361 if (count != 1) {
362 fxp_internal_error("REALPATH returned name count != 1\n");
363 return NULL;
364 }
365 sftp_pkt_getstring(pktin, &path, &len);
366 if (!path) {
367 fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
368 return NULL;
369 }
370 path = mkstr(path, len);
371 sftp_pkt_free(pktin);
372 return path;
373 } else {
374 fxp_got_status(pktin);
375 return NULL;
376 }
377}
378
379/*
380 * Open a file.
381 */
32874aea 382struct fxp_handle *fxp_open(char *path, int type)
383{
4c7f0d61 384 struct sftp_packet *pktin, *pktout;
385 int id;
386
387 pktout = sftp_pkt_init(SSH_FXP_OPEN);
388 sftp_pkt_adduint32(pktout, 0x567); /* request id */
389 sftp_pkt_addstring(pktout, path);
390 sftp_pkt_adduint32(pktout, type);
391 sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */
392 sftp_send(pktout);
393 pktin = sftp_recv();
394 id = sftp_pkt_getuint32(pktin);
395 if (id != 0x567) {
396 fxp_internal_error("request ID mismatch\n");
397 return NULL;
398 }
399 if (pktin->type == SSH_FXP_HANDLE) {
4c7f0d61 400 char *hstring;
401 struct fxp_handle *handle;
402 int len;
403
404 sftp_pkt_getstring(pktin, &hstring, &len);
405 if (!hstring) {
406 fxp_internal_error("OPEN returned malformed FXP_HANDLE\n");
407 return NULL;
408 }
409 handle = smalloc(sizeof(struct fxp_handle));
410 handle->hstring = mkstr(hstring, len);
f9e162aa 411 handle->hlen = len;
4c7f0d61 412 sftp_pkt_free(pktin);
413 return handle;
414 } else {
415 fxp_got_status(pktin);
416 return NULL;
417 }
418}
419
420/*
421 * Open a directory.
422 */
32874aea 423struct fxp_handle *fxp_opendir(char *path)
424{
4c7f0d61 425 struct sftp_packet *pktin, *pktout;
426 int id;
427
428 pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
429 sftp_pkt_adduint32(pktout, 0x456); /* request id */
430 sftp_pkt_addstring(pktout, path);
431 sftp_send(pktout);
432 pktin = sftp_recv();
433 id = sftp_pkt_getuint32(pktin);
434 if (id != 0x456) {
435 fxp_internal_error("request ID mismatch\n");
436 return NULL;
437 }
438 if (pktin->type == SSH_FXP_HANDLE) {
4c7f0d61 439 char *hstring;
440 struct fxp_handle *handle;
441 int len;
442
443 sftp_pkt_getstring(pktin, &hstring, &len);
444 if (!hstring) {
445 fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n");
446 return NULL;
447 }
448 handle = smalloc(sizeof(struct fxp_handle));
449 handle->hstring = mkstr(hstring, len);
f9e162aa 450 handle->hlen = len;
4c7f0d61 451 sftp_pkt_free(pktin);
452 return handle;
453 } else {
454 fxp_got_status(pktin);
455 return NULL;
456 }
457}
458
459/*
460 * Close a file/dir.
461 */
32874aea 462void fxp_close(struct fxp_handle *handle)
463{
4c7f0d61 464 struct sftp_packet *pktin, *pktout;
465 int id;
466
467 pktout = sftp_pkt_init(SSH_FXP_CLOSE);
468 sftp_pkt_adduint32(pktout, 0x789); /* request id */
f9e162aa 469 sftp_pkt_addstring_start(pktout);
470 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
4c7f0d61 471 sftp_send(pktout);
472 pktin = sftp_recv();
473 id = sftp_pkt_getuint32(pktin);
474 if (id != 0x789) {
475 fxp_internal_error("request ID mismatch\n");
476 return;
477 }
478 fxp_got_status(pktin);
479 sfree(handle->hstring);
480 sfree(handle);
481}
482
483/*
484 * Read from a file. Returns the number of bytes read, or -1 on an
485 * error, or possibly 0 if EOF. (I'm not entirely sure whether it
486 * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
487 * error indicator. It might even depend on the SFTP server.)
488 */
32874aea 489int fxp_read(struct fxp_handle *handle, char *buffer, uint64 offset,
490 int len)
491{
4c7f0d61 492 struct sftp_packet *pktin, *pktout;
493 int id;
494
495 pktout = sftp_pkt_init(SSH_FXP_READ);
496 sftp_pkt_adduint32(pktout, 0xBCD); /* request id */
f9e162aa 497 sftp_pkt_addstring_start(pktout);
498 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
4c7f0d61 499 sftp_pkt_adduint64(pktout, offset);
500 sftp_pkt_adduint32(pktout, len);
501 sftp_send(pktout);
502 pktin = sftp_recv();
503 id = sftp_pkt_getuint32(pktin);
504 if (id != 0xBCD) {
505 fxp_internal_error("request ID mismatch");
4a8fc3c4 506 return -1;
4c7f0d61 507 }
508 if (pktin->type == SSH_FXP_DATA) {
509 char *str;
510 int rlen;
511
512 sftp_pkt_getstring(pktin, &str, &rlen);
513
514 if (rlen > len || rlen < 0) {
515 fxp_internal_error("READ returned more bytes than requested");
516 return -1;
517 }
518
519 memcpy(buffer, str, rlen);
520 sfree(pktin);
521 return rlen;
522 } else {
523 fxp_got_status(pktin);
524 return -1;
525 }
526}
527
528/*
529 * Read from a directory.
530 */
32874aea 531struct fxp_names *fxp_readdir(struct fxp_handle *handle)
532{
4c7f0d61 533 struct sftp_packet *pktin, *pktout;
534 int id;
535
536 pktout = sftp_pkt_init(SSH_FXP_READDIR);
537 sftp_pkt_adduint32(pktout, 0xABC); /* request id */
f9e162aa 538 sftp_pkt_addstring_start(pktout);
539 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
4c7f0d61 540 sftp_send(pktout);
541 pktin = sftp_recv();
542 id = sftp_pkt_getuint32(pktin);
543 if (id != 0xABC) {
544 fxp_internal_error("request ID mismatch\n");
4a8fc3c4 545 return NULL;
4c7f0d61 546 }
547 if (pktin->type == SSH_FXP_NAME) {
548 struct fxp_names *ret;
549 int i;
550 ret = smalloc(sizeof(struct fxp_names));
551 ret->nnames = sftp_pkt_getuint32(pktin);
552 ret->names = smalloc(ret->nnames * sizeof(struct fxp_name));
553 for (i = 0; i < ret->nnames; i++) {
554 char *str;
555 int len;
556 sftp_pkt_getstring(pktin, &str, &len);
557 ret->names[i].filename = mkstr(str, len);
558 sftp_pkt_getstring(pktin, &str, &len);
559 ret->names[i].longname = mkstr(str, len);
560 ret->names[i].attrs = sftp_pkt_getattrs(pktin);
561 }
562 return ret;
563 } else {
564 fxp_got_status(pktin);
565 return NULL;
566 }
567}
568
569/*
570 * Write to a file. Returns 0 on error, 1 on OK.
571 */
32874aea 572int fxp_write(struct fxp_handle *handle, char *buffer, uint64 offset,
573 int len)
574{
4c7f0d61 575 struct sftp_packet *pktin, *pktout;
576 int id;
577
578 pktout = sftp_pkt_init(SSH_FXP_WRITE);
579 sftp_pkt_adduint32(pktout, 0xDCB); /* request id */
f9e162aa 580 sftp_pkt_addstring_start(pktout);
581 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
4c7f0d61 582 sftp_pkt_adduint64(pktout, offset);
583 sftp_pkt_addstring_start(pktout);
584 sftp_pkt_addstring_data(pktout, buffer, len);
585 sftp_send(pktout);
586 pktin = sftp_recv();
587 id = sftp_pkt_getuint32(pktin);
4a8fc3c4 588 if (id != 0xDCB) {
589 fxp_internal_error("request ID mismatch\n");
d8770b12 590 return 0;
4a8fc3c4 591 }
4c7f0d61 592 fxp_got_status(pktin);
593 return fxp_errtype == SSH_FX_OK;
594}
595
596/*
597 * Free up an fxp_names structure.
598 */
32874aea 599void fxp_free_names(struct fxp_names *names)
600{
4c7f0d61 601 int i;
602
603 for (i = 0; i < names->nnames; i++) {
604 sfree(names->names[i].filename);
605 sfree(names->names[i].longname);
606 }
607 sfree(names->names);
608 sfree(names);
609}