SFTP client now successfully handles cd, ls, get and put.
[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 <assert.h>
8 #include <unistd.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. FIXME: change for PuTTY.
172 */
173 int tossh, fromssh;
174 int io_init(void) {
175 int to[2], from[2];
176 int pid;
177
178 assert(pipe(to) == 0);
179 assert(pipe(from) == 0);
180 pid = fork();
181 assert(pid >= 0);
182 if (pid == 0) {
183 /* We are child. Dup one end of each pipe to our std[io],
184 * close other end, exec. */
185 close(0); dup2(to[0], 0); close(to[1]);
186 close(1); dup2(from[1], 1); close(from[0]);
187 execl("/home/simon/src/openssh/openssh_cvs/prefix/bin/ssh", "ssh", "-2", "simon@localhost", "-s", "sftp", NULL);
188 //execl("/root/ssh-research/ssh-2.4.0/apps/ssh/sftp-server2", "sftp-server2", NULL);
189 //execl("/usr/lib/sftp-server", "sftp-server", NULL);
190 assert(0); /* bomb out if not */
191 } else {
192 /* We are parent. Close wrong end of each pipe, assign to glob vars. */
193 close(to[0]); tossh = to[1];
194 close(from[1]); fromssh = from[0];
195 }
196 }
197 int io_finish(void) {
198 int pid, status;
199 close(tossh);
200 close(fromssh);
201 pid = wait(&status);
202 }
203 int sftp_send(struct sftp_packet *pkt) {
204 char x[4];
205 PUT_32BIT(x, pkt->length);
206 assert(4 == write(tossh, x, 4));
207 assert(pkt->length = write(tossh, pkt->data, pkt->length));
208 sftp_pkt_free(pkt);
209 }
210 struct sftp_packet *sftp_recv(void) {
211 struct sftp_packet *pkt;
212 char x[4];
213 int p, ret;
214
215 for (p = 0; p < 4 ;) {
216 ret = read(fromssh, x+p, 4-p);
217 assert(ret >= 0);
218 if (ret == 0)
219 return NULL;
220 p += ret;
221 }
222
223 pkt = smalloc(sizeof(struct sftp_packet));
224 pkt->savedpos = 0;
225 pkt->length = pkt->maxlen = GET_32BIT(x);
226 pkt->data = smalloc(pkt->length);
227
228 for (p = 0; p < pkt->length ;) {
229 ret = read(fromssh, pkt->data+p, pkt->length-p);
230 assert(ret >= 0);
231 if (ret == 0) {
232 sftp_pkt_free(pkt);
233 return NULL;
234 }
235 p += ret;
236 }
237
238 pkt->type = sftp_pkt_getbyte(pkt);
239
240 return pkt;
241 }
242
243 /* ----------------------------------------------------------------------
244 * String handling routines.
245 */
246
247 static char *mkstr(char *s, int len) {
248 char *p = smalloc(len+1);
249 memcpy(p, s, len);
250 p[len] = '\0';
251 return p;
252 }
253
254 /* ----------------------------------------------------------------------
255 * SFTP primitives.
256 */
257
258 static const char *fxp_error_message;
259 static int fxp_errtype;
260
261 /*
262 * Deal with (and free) an FXP_STATUS packet. Return 1 if
263 * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
264 * Also place the status into fxp_errtype.
265 */
266 static int fxp_got_status(struct sftp_packet *pktin) {
267 static const char *const messages[] = {
268 /* SSH_FX_OK. The only time we will display a _message_ for this
269 * is if we were expecting something other than FXP_STATUS on
270 * success, so this is actually an error message! */
271 "unexpected OK response",
272 "end of file",
273 "no such file or directory",
274 "permission denied",
275 "failure",
276 "bad message",
277 "no connection",
278 "connection lost",
279 "operation unsupported",
280 };
281
282 if (pktin->type != SSH_FXP_STATUS) {
283 fxp_error_message = "expected FXP_STATUS packet";
284 fxp_errtype = -1;
285 } else {
286 fxp_errtype = sftp_pkt_getuint32(pktin);
287 if (fxp_errtype < 0 ||
288 fxp_errtype >= sizeof(messages)/sizeof(*messages))
289 fxp_error_message = "unknown error code";
290 else
291 fxp_error_message = messages[fxp_errtype];
292 }
293
294 if (fxp_errtype == SSH_FX_OK)
295 return 1;
296 else if (fxp_errtype == SSH_FX_EOF)
297 return 0;
298 else
299 return -1;
300 }
301
302 static int fxp_internal_error(char *msg) {
303 fxp_error_message = msg;
304 fxp_errtype = -1;
305 }
306
307 const char *fxp_error(void) {
308 return fxp_error_message;
309 }
310
311 int fxp_error_type(void) {
312 return fxp_errtype;
313 }
314
315 /*
316 * Perform exchange of init/version packets. Return 0 on failure.
317 */
318 int fxp_init(void) {
319 struct sftp_packet *pktout, *pktin;
320 int remotever;
321
322 pktout = sftp_pkt_init(SSH_FXP_INIT);
323 sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
324 sftp_send(pktout);
325
326 pktin = sftp_recv();
327 if (pktin->type != SSH_FXP_VERSION) {
328 fxp_internal_error("did not receive FXP_VERSION");
329 return 0;
330 }
331 remotever = sftp_pkt_getuint32(pktin);
332 if (remotever > SFTP_PROTO_VERSION) {
333 fxp_internal_error("remote protocol is more advanced than we support");
334 return 0;
335 }
336 /*
337 * In principle, this packet might also contain extension-
338 * string pairs. We should work through them and look for any
339 * we recognise. In practice we don't currently do so because
340 * we know we don't recognise _any_.
341 */
342 sftp_pkt_free(pktin);
343
344 return 1;
345 }
346
347 /*
348 * Canonify a pathname.
349 */
350 char *fxp_realpath(char *path) {
351 struct sftp_packet *pktin, *pktout;
352 int id;
353
354 pktout = sftp_pkt_init(SSH_FXP_REALPATH);
355 sftp_pkt_adduint32(pktout, 0x123); /* request id */
356 sftp_pkt_addstring_start(pktout);
357 sftp_pkt_addstring_str(pktout, path);
358 sftp_send(pktout);
359 pktin = sftp_recv();
360 id = sftp_pkt_getuint32(pktin);
361 if (id != 0x123) {
362 fxp_internal_error("request ID mismatch\n");
363 return NULL;
364 }
365 if (pktin->type == SSH_FXP_NAME) {
366 int count;
367 char *path;
368 int len;
369
370 count = sftp_pkt_getuint32(pktin);
371 if (count != 1) {
372 fxp_internal_error("REALPATH returned name count != 1\n");
373 return NULL;
374 }
375 sftp_pkt_getstring(pktin, &path, &len);
376 if (!path) {
377 fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
378 return NULL;
379 }
380 path = mkstr(path, len);
381 sftp_pkt_free(pktin);
382 return path;
383 } else {
384 fxp_got_status(pktin);
385 return NULL;
386 }
387 }
388
389 /*
390 * Open a file.
391 */
392 struct fxp_handle *fxp_open(char *path, int type) {
393 struct sftp_packet *pktin, *pktout;
394 int id;
395
396 pktout = sftp_pkt_init(SSH_FXP_OPEN);
397 sftp_pkt_adduint32(pktout, 0x567); /* request id */
398 sftp_pkt_addstring(pktout, path);
399 sftp_pkt_adduint32(pktout, type);
400 sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */
401 sftp_send(pktout);
402 pktin = sftp_recv();
403 id = sftp_pkt_getuint32(pktin);
404 if (id != 0x567) {
405 fxp_internal_error("request ID mismatch\n");
406 return NULL;
407 }
408 if (pktin->type == SSH_FXP_HANDLE) {
409 int count;
410 char *hstring;
411 struct fxp_handle *handle;
412 int len;
413
414 sftp_pkt_getstring(pktin, &hstring, &len);
415 if (!hstring) {
416 fxp_internal_error("OPEN returned malformed FXP_HANDLE\n");
417 return NULL;
418 }
419 handle = smalloc(sizeof(struct fxp_handle));
420 handle->hstring = mkstr(hstring, len);
421 handle->hlen = len;
422 sftp_pkt_free(pktin);
423 return handle;
424 } else {
425 fxp_got_status(pktin);
426 return NULL;
427 }
428 }
429
430 /*
431 * Open a directory.
432 */
433 struct fxp_handle *fxp_opendir(char *path) {
434 struct sftp_packet *pktin, *pktout;
435 int id;
436
437 pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
438 sftp_pkt_adduint32(pktout, 0x456); /* request id */
439 sftp_pkt_addstring(pktout, path);
440 sftp_send(pktout);
441 pktin = sftp_recv();
442 id = sftp_pkt_getuint32(pktin);
443 if (id != 0x456) {
444 fxp_internal_error("request ID mismatch\n");
445 return NULL;
446 }
447 if (pktin->type == SSH_FXP_HANDLE) {
448 int count;
449 char *hstring;
450 struct fxp_handle *handle;
451 int len;
452
453 sftp_pkt_getstring(pktin, &hstring, &len);
454 if (!hstring) {
455 fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n");
456 return NULL;
457 }
458 handle = smalloc(sizeof(struct fxp_handle));
459 handle->hstring = mkstr(hstring, len);
460 handle->hlen = len;
461 sftp_pkt_free(pktin);
462 return handle;
463 } else {
464 fxp_got_status(pktin);
465 return NULL;
466 }
467 }
468
469 /*
470 * Close a file/dir.
471 */
472 void fxp_close(struct fxp_handle *handle) {
473 struct sftp_packet *pktin, *pktout;
474 int id;
475
476 pktout = sftp_pkt_init(SSH_FXP_CLOSE);
477 sftp_pkt_adduint32(pktout, 0x789); /* request id */
478 sftp_pkt_addstring_start(pktout);
479 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
480 sftp_send(pktout);
481 pktin = sftp_recv();
482 id = sftp_pkt_getuint32(pktin);
483 if (id != 0x789) {
484 fxp_internal_error("request ID mismatch\n");
485 return;
486 }
487 fxp_got_status(pktin);
488 sfree(handle->hstring);
489 sfree(handle);
490 }
491
492 /*
493 * Read from a file. Returns the number of bytes read, or -1 on an
494 * error, or possibly 0 if EOF. (I'm not entirely sure whether it
495 * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
496 * error indicator. It might even depend on the SFTP server.)
497 */
498 int fxp_read(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
499 struct sftp_packet *pktin, *pktout;
500 int id;
501
502 pktout = sftp_pkt_init(SSH_FXP_READ);
503 sftp_pkt_adduint32(pktout, 0xBCD); /* request id */
504 sftp_pkt_addstring_start(pktout);
505 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
506 sftp_pkt_adduint64(pktout, offset);
507 sftp_pkt_adduint32(pktout, len);
508 sftp_send(pktout);
509 pktin = sftp_recv();
510 id = sftp_pkt_getuint32(pktin);
511 if (id != 0xBCD) {
512 fxp_internal_error("request ID mismatch");
513 return;
514 }
515 if (pktin->type == SSH_FXP_DATA) {
516 char *str;
517 int rlen;
518
519 sftp_pkt_getstring(pktin, &str, &rlen);
520
521 if (rlen > len || rlen < 0) {
522 fxp_internal_error("READ returned more bytes than requested");
523 return -1;
524 }
525
526 memcpy(buffer, str, rlen);
527 sfree(pktin);
528 return rlen;
529 } else {
530 fxp_got_status(pktin);
531 return -1;
532 }
533 }
534
535 /*
536 * Read from a directory.
537 */
538 struct fxp_names *fxp_readdir(struct fxp_handle *handle) {
539 struct sftp_packet *pktin, *pktout;
540 int id;
541
542 pktout = sftp_pkt_init(SSH_FXP_READDIR);
543 sftp_pkt_adduint32(pktout, 0xABC); /* request id */
544 sftp_pkt_addstring_start(pktout);
545 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
546 sftp_send(pktout);
547 pktin = sftp_recv();
548 id = sftp_pkt_getuint32(pktin);
549 if (id != 0xABC) {
550 fxp_internal_error("request ID mismatch\n");
551 return;
552 }
553 if (pktin->type == SSH_FXP_NAME) {
554 struct fxp_names *ret;
555 int i;
556 ret = smalloc(sizeof(struct fxp_names));
557 ret->nnames = sftp_pkt_getuint32(pktin);
558 ret->names = smalloc(ret->nnames * sizeof(struct fxp_name));
559 for (i = 0; i < ret->nnames; i++) {
560 char *str;
561 int len;
562 sftp_pkt_getstring(pktin, &str, &len);
563 ret->names[i].filename = mkstr(str, len);
564 sftp_pkt_getstring(pktin, &str, &len);
565 ret->names[i].longname = mkstr(str, len);
566 ret->names[i].attrs = sftp_pkt_getattrs(pktin);
567 }
568 return ret;
569 } else {
570 fxp_got_status(pktin);
571 return NULL;
572 }
573 }
574
575 /*
576 * Write to a file. Returns 0 on error, 1 on OK.
577 */
578 int fxp_write(struct fxp_handle *handle, char *buffer, uint64 offset, int len) {
579 struct sftp_packet *pktin, *pktout;
580 int id;
581
582 pktout = sftp_pkt_init(SSH_FXP_WRITE);
583 sftp_pkt_adduint32(pktout, 0xDCB); /* request id */
584 sftp_pkt_addstring_start(pktout);
585 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
586 sftp_pkt_adduint64(pktout, offset);
587 sftp_pkt_addstring_start(pktout);
588 sftp_pkt_addstring_data(pktout, buffer, len);
589 sftp_send(pktout);
590 pktin = sftp_recv();
591 id = sftp_pkt_getuint32(pktin);
592 fxp_got_status(pktin);
593 return fxp_errtype == SSH_FX_OK;
594 }
595
596 /*
597 * Free up an fxp_names structure.
598 */
599 void fxp_free_names(struct fxp_names *names) {
600 int i;
601
602 for (i = 0; i < names->nnames; i++) {
603 sfree(names->names[i].filename);
604 sfree(names->names[i].longname);
605 }
606 sfree(names->names);
607 sfree(names);
608 }