psftp now works as part of the PuTTY suite
[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->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 /*
310 * Canonify a pathname.
311 */
312 char *fxp_realpath(char *path) {
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);
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 */
354 struct 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);
383 handle->hlen = len;
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 */
395 struct 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);
422 handle->hlen = len;
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 */
434 void 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 */
440 sftp_pkt_addstring_start(pktout);
441 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
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 */
460 int 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 */
466 sftp_pkt_addstring_start(pktout);
467 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
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");
475 return -1;
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 */
500 struct 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 */
506 sftp_pkt_addstring_start(pktout);
507 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
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");
513 return NULL;
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 */
540 int 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 */
546 sftp_pkt_addstring_start(pktout);
547 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
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);
554 if (id != 0xDCB) {
555 fxp_internal_error("request ID mismatch\n");
556 return NULL;
557 }
558 fxp_got_status(pktin);
559 return fxp_errtype == SSH_FX_OK;
560 }
561
562 /*
563 * Free up an fxp_names structure.
564 */
565 void 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 }