]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - sftp.c
SFTP client now successfully handles cd, ls, get and put.
[PuTTY.git] / 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 }