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