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