Patches to prevent a couple of silly crashes
[u/mdw/putty] / sftp.c
1 /*
2 * sftp.c: SFTP generic client code.
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <assert.h>
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
29 struct sftp_packet {
30 char *data;
31 int length, maxlen;
32 int savedpos;
33 int type;
34 };
35
36 /* ----------------------------------------------------------------------
37 * SFTP packet construction functions.
38 */
39 static 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 }
45 static 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 }
50 static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte) {
51 sftp_pkt_adddata(pkt, &byte, 1);
52 }
53 static 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 }
63 static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value) {
64 sftp_pkt_adddata(pkt, &value, 1);
65 }
66 static 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 }
71 static 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 }
77 static void sftp_pkt_addstring_start(struct sftp_packet *pkt) {
78 sftp_pkt_adduint32(pkt, 0);
79 pkt->savedpos = pkt->length;
80 }
81 static 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 }
86 static 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 }
92 static 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
101 static 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 }
109 static 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 }
117 static 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 }
129 static 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 }
165 static void sftp_pkt_free(struct sftp_packet *pkt) {
166 if (pkt->data) sfree(pkt->data);
167 sfree(pkt);
168 }
169
170 /* ----------------------------------------------------------------------
171 * Send and receive packet functions.
172 */
173 int sftp_send(struct sftp_packet *pkt) {
174 int ret;
175 char x[4];
176 PUT_32BIT(x, pkt->length);
177 ret = (sftp_senddata(x, 4) &&
178 sftp_senddata(pkt->data, pkt->length));
179 sftp_pkt_free(pkt);
180 return ret;
181 }
182 struct sftp_packet *sftp_recv(void) {
183 struct sftp_packet *pkt;
184 char x[4];
185 int p, ret;
186
187 if (!sftp_recvdata(x, 4))
188 return NULL;
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
195 if (!sftp_recvdata(pkt->data, pkt->length)) {
196 sftp_pkt_free(pkt);
197 return NULL;
198 }
199
200 pkt->type = sftp_pkt_getbyte(pkt);
201
202 return pkt;
203 }
204
205 /* ----------------------------------------------------------------------
206 * String handling routines.
207 */
208
209 static 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
220 static const char *fxp_error_message;
221 static 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 */
228 static 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
264 static void fxp_internal_error(char *msg) {
265 fxp_error_message = msg;
266 fxp_errtype = -1;
267 }
268
269 const char *fxp_error(void) {
270 return fxp_error_message;
271 }
272
273 int fxp_error_type(void) {
274 return fxp_errtype;
275 }
276
277 /*
278 * Perform exchange of init/version packets. Return 0 on failure.
279 */
280 int 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) {
290 fxp_internal_error("could not connect");
291 return 0;
292 }
293 if (pktin->type != SSH_FXP_VERSION) {
294 fxp_internal_error("did not receive FXP_VERSION");
295 return 0;
296 }
297 remotever = sftp_pkt_getuint32(pktin);
298 if (remotever > SFTP_PROTO_VERSION) {
299 fxp_internal_error("remote protocol is more advanced than we support");
300 return 0;
301 }
302 /*
303 * In principle, this packet might also contain extension-
304 * string pairs. We should work through them and look for any
305 * we recognise. In practice we don't currently do so because
306 * we know we don't recognise _any_.
307 */
308 sftp_pkt_free(pktin);
309
310 return 1;
311 }
312
313 /*
314 * Canonify a pathname.
315 */
316 char *fxp_realpath(char *path) {
317 struct sftp_packet *pktin, *pktout;
318 int id;
319
320 pktout = sftp_pkt_init(SSH_FXP_REALPATH);
321 sftp_pkt_adduint32(pktout, 0x123); /* request id */
322 sftp_pkt_addstring_start(pktout);
323 sftp_pkt_addstring_str(pktout, path);
324 sftp_send(pktout);
325 pktin = sftp_recv();
326 id = sftp_pkt_getuint32(pktin);
327 if (id != 0x123) {
328 fxp_internal_error("request ID mismatch\n");
329 return NULL;
330 }
331 if (pktin->type == SSH_FXP_NAME) {
332 int count;
333 char *path;
334 int len;
335
336 count = sftp_pkt_getuint32(pktin);
337 if (count != 1) {
338 fxp_internal_error("REALPATH returned name count != 1\n");
339 return NULL;
340 }
341 sftp_pkt_getstring(pktin, &path, &len);
342 if (!path) {
343 fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
344 return NULL;
345 }
346 path = mkstr(path, len);
347 sftp_pkt_free(pktin);
348 return path;
349 } else {
350 fxp_got_status(pktin);
351 return NULL;
352 }
353 }
354
355 /*
356 * Open a file.
357 */
358 struct fxp_handle *fxp_open(char *path, int type) {
359 struct sftp_packet *pktin, *pktout;
360 int id;
361
362 pktout = sftp_pkt_init(SSH_FXP_OPEN);
363 sftp_pkt_adduint32(pktout, 0x567); /* request id */
364 sftp_pkt_addstring(pktout, path);
365 sftp_pkt_adduint32(pktout, type);
366 sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */
367 sftp_send(pktout);
368 pktin = sftp_recv();
369 id = sftp_pkt_getuint32(pktin);
370 if (id != 0x567) {
371 fxp_internal_error("request ID mismatch\n");
372 return NULL;
373 }
374 if (pktin->type == SSH_FXP_HANDLE) {
375 int count;
376 char *hstring;
377 struct fxp_handle *handle;
378 int len;
379
380 sftp_pkt_getstring(pktin, &hstring, &len);
381 if (!hstring) {
382 fxp_internal_error("OPEN returned malformed FXP_HANDLE\n");
383 return NULL;
384 }
385 handle = smalloc(sizeof(struct fxp_handle));
386 handle->hstring = mkstr(hstring, len);
387 handle->hlen = len;
388 sftp_pkt_free(pktin);
389 return handle;
390 } else {
391 fxp_got_status(pktin);
392 return NULL;
393 }
394 }
395
396 /*
397 * Open a directory.
398 */
399 struct fxp_handle *fxp_opendir(char *path) {
400 struct sftp_packet *pktin, *pktout;
401 int id;
402
403 pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
404 sftp_pkt_adduint32(pktout, 0x456); /* request id */
405 sftp_pkt_addstring(pktout, path);
406 sftp_send(pktout);
407 pktin = sftp_recv();
408 id = sftp_pkt_getuint32(pktin);
409 if (id != 0x456) {
410 fxp_internal_error("request ID mismatch\n");
411 return NULL;
412 }
413 if (pktin->type == SSH_FXP_HANDLE) {
414 int count;
415 char *hstring;
416 struct fxp_handle *handle;
417 int len;
418
419 sftp_pkt_getstring(pktin, &hstring, &len);
420 if (!hstring) {
421 fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n");
422 return NULL;
423 }
424 handle = smalloc(sizeof(struct fxp_handle));
425 handle->hstring = mkstr(hstring, len);
426 handle->hlen = len;
427 sftp_pkt_free(pktin);
428 return handle;
429 } else {
430 fxp_got_status(pktin);
431 return NULL;
432 }
433 }
434
435 /*
436 * Close a file/dir.
437 */
438 void fxp_close(struct fxp_handle *handle) {
439 struct sftp_packet *pktin, *pktout;
440 int id;
441
442 pktout = sftp_pkt_init(SSH_FXP_CLOSE);
443 sftp_pkt_adduint32(pktout, 0x789); /* request id */
444 sftp_pkt_addstring_start(pktout);
445 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
446 sftp_send(pktout);
447 pktin = sftp_recv();
448 id = sftp_pkt_getuint32(pktin);
449 if (id != 0x789) {
450 fxp_internal_error("request ID mismatch\n");
451 return;
452 }
453 fxp_got_status(pktin);
454 sfree(handle->hstring);
455 sfree(handle);
456 }
457
458 /*
459 * Read from a file. Returns the number of bytes read, or -1 on an
460 * error, or possibly 0 if EOF. (I'm not entirely sure whether it
461 * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
462 * error indicator. It might even depend on the SFTP server.)
463 */
464 int fxp_read(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
465 struct sftp_packet *pktin, *pktout;
466 int id;
467
468 pktout = sftp_pkt_init(SSH_FXP_READ);
469 sftp_pkt_adduint32(pktout, 0xBCD); /* request id */
470 sftp_pkt_addstring_start(pktout);
471 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
472 sftp_pkt_adduint64(pktout, offset);
473 sftp_pkt_adduint32(pktout, len);
474 sftp_send(pktout);
475 pktin = sftp_recv();
476 id = sftp_pkt_getuint32(pktin);
477 if (id != 0xBCD) {
478 fxp_internal_error("request ID mismatch");
479 return -1;
480 }
481 if (pktin->type == SSH_FXP_DATA) {
482 char *str;
483 int rlen;
484
485 sftp_pkt_getstring(pktin, &str, &rlen);
486
487 if (rlen > len || rlen < 0) {
488 fxp_internal_error("READ returned more bytes than requested");
489 return -1;
490 }
491
492 memcpy(buffer, str, rlen);
493 sfree(pktin);
494 return rlen;
495 } else {
496 fxp_got_status(pktin);
497 return -1;
498 }
499 }
500
501 /*
502 * Read from a directory.
503 */
504 struct fxp_names *fxp_readdir(struct fxp_handle *handle) {
505 struct sftp_packet *pktin, *pktout;
506 int id;
507
508 pktout = sftp_pkt_init(SSH_FXP_READDIR);
509 sftp_pkt_adduint32(pktout, 0xABC); /* request id */
510 sftp_pkt_addstring_start(pktout);
511 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
512 sftp_send(pktout);
513 pktin = sftp_recv();
514 id = sftp_pkt_getuint32(pktin);
515 if (id != 0xABC) {
516 fxp_internal_error("request ID mismatch\n");
517 return NULL;
518 }
519 if (pktin->type == SSH_FXP_NAME) {
520 struct fxp_names *ret;
521 int i;
522 ret = smalloc(sizeof(struct fxp_names));
523 ret->nnames = sftp_pkt_getuint32(pktin);
524 ret->names = smalloc(ret->nnames * sizeof(struct fxp_name));
525 for (i = 0; i < ret->nnames; i++) {
526 char *str;
527 int len;
528 sftp_pkt_getstring(pktin, &str, &len);
529 ret->names[i].filename = mkstr(str, len);
530 sftp_pkt_getstring(pktin, &str, &len);
531 ret->names[i].longname = mkstr(str, len);
532 ret->names[i].attrs = sftp_pkt_getattrs(pktin);
533 }
534 return ret;
535 } else {
536 fxp_got_status(pktin);
537 return NULL;
538 }
539 }
540
541 /*
542 * Write to a file. Returns 0 on error, 1 on OK.
543 */
544 int fxp_write(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
545 struct sftp_packet *pktin, *pktout;
546 int id;
547
548 pktout = sftp_pkt_init(SSH_FXP_WRITE);
549 sftp_pkt_adduint32(pktout, 0xDCB); /* request id */
550 sftp_pkt_addstring_start(pktout);
551 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
552 sftp_pkt_adduint64(pktout, offset);
553 sftp_pkt_addstring_start(pktout);
554 sftp_pkt_addstring_data(pktout, buffer, len);
555 sftp_send(pktout);
556 pktin = sftp_recv();
557 id = sftp_pkt_getuint32(pktin);
558 if (id != 0xDCB) {
559 fxp_internal_error("request ID mismatch\n");
560 return NULL;
561 }
562 fxp_got_status(pktin);
563 return fxp_errtype == SSH_FX_OK;
564 }
565
566 /*
567 * Free up an fxp_names structure.
568 */
569 void fxp_free_names(struct fxp_names *names) {
570 int i;
571
572 for (i = 0; i < names->nnames; i++) {
573 sfree(names->names[i].filename);
574 sfree(names->names[i].longname);
575 }
576 sfree(names->names);
577 sfree(names);
578 }