Patch to PSFTP: implement mkdir, rmdir, rm and scripting. Still to
[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 static const char *fxp_error_message;
37 static int fxp_errtype;
38
39 /* ----------------------------------------------------------------------
40 * SFTP packet construction functions.
41 */
42 static void sftp_pkt_ensure(struct sftp_packet *pkt, int length)
43 {
44 if (pkt->maxlen < length) {
45 pkt->maxlen = length + 256;
46 pkt->data = srealloc(pkt->data, pkt->maxlen);
47 }
48 }
49 static void sftp_pkt_adddata(struct sftp_packet *pkt, void *data, int len)
50 {
51 pkt->length += len;
52 sftp_pkt_ensure(pkt, pkt->length);
53 memcpy(pkt->data + pkt->length - len, data, len);
54 }
55 static void sftp_pkt_addbyte(struct sftp_packet *pkt, unsigned char byte)
56 {
57 sftp_pkt_adddata(pkt, &byte, 1);
58 }
59 static struct sftp_packet *sftp_pkt_init(int pkt_type)
60 {
61 struct sftp_packet *pkt;
62 pkt = smalloc(sizeof(struct sftp_packet));
63 pkt->data = NULL;
64 pkt->savedpos = -1;
65 pkt->length = 0;
66 pkt->maxlen = 0;
67 sftp_pkt_addbyte(pkt, (unsigned char) pkt_type);
68 fxp_error_message = NULL;
69 return pkt;
70 }
71 static void sftp_pkt_addbool(struct sftp_packet *pkt, unsigned char value)
72 {
73 sftp_pkt_adddata(pkt, &value, 1);
74 }
75 static void sftp_pkt_adduint32(struct sftp_packet *pkt,
76 unsigned long value)
77 {
78 unsigned char x[4];
79 PUT_32BIT(x, value);
80 sftp_pkt_adddata(pkt, x, 4);
81 }
82 static void sftp_pkt_adduint64(struct sftp_packet *pkt, uint64 value)
83 {
84 unsigned char x[8];
85 PUT_32BIT(x, value.hi);
86 PUT_32BIT(x + 4, value.lo);
87 sftp_pkt_adddata(pkt, x, 8);
88 }
89 static void sftp_pkt_addstring_start(struct sftp_packet *pkt)
90 {
91 sftp_pkt_adduint32(pkt, 0);
92 pkt->savedpos = pkt->length;
93 }
94 static void sftp_pkt_addstring_str(struct sftp_packet *pkt, char *data)
95 {
96 sftp_pkt_adddata(pkt, data, strlen(data));
97 PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
98 }
99 static void sftp_pkt_addstring_data(struct sftp_packet *pkt,
100 char *data, int len)
101 {
102 sftp_pkt_adddata(pkt, data, len);
103 PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
104 }
105 static void sftp_pkt_addstring(struct sftp_packet *pkt, char *data)
106 {
107 sftp_pkt_addstring_start(pkt);
108 sftp_pkt_addstring_str(pkt, data);
109 }
110
111 /* ----------------------------------------------------------------------
112 * SFTP packet decode functions.
113 */
114
115 static unsigned char sftp_pkt_getbyte(struct sftp_packet *pkt)
116 {
117 unsigned char value;
118 if (pkt->length - pkt->savedpos < 1)
119 return 0; /* arrgh, no way to decline (FIXME?) */
120 value = (unsigned char) pkt->data[pkt->savedpos];
121 pkt->savedpos++;
122 return value;
123 }
124 static unsigned long sftp_pkt_getuint32(struct sftp_packet *pkt)
125 {
126 unsigned long value;
127 if (pkt->length - pkt->savedpos < 4)
128 return 0; /* arrgh, no way to decline (FIXME?) */
129 value = GET_32BIT(pkt->data + pkt->savedpos);
130 pkt->savedpos += 4;
131 return value;
132 }
133 static void sftp_pkt_getstring(struct sftp_packet *pkt,
134 char **p, int *length)
135 {
136 *p = NULL;
137 if (pkt->length - pkt->savedpos < 4)
138 return;
139 *length = GET_32BIT(pkt->data + pkt->savedpos);
140 pkt->savedpos += 4;
141 if (pkt->length - pkt->savedpos < *length)
142 return;
143 *p = pkt->data + pkt->savedpos;
144 pkt->savedpos += *length;
145 }
146 static struct fxp_attrs sftp_pkt_getattrs(struct sftp_packet *pkt)
147 {
148 struct fxp_attrs ret;
149 ret.flags = sftp_pkt_getuint32(pkt);
150 if (ret.flags & SSH_FILEXFER_ATTR_SIZE) {
151 unsigned long hi, lo;
152 hi = sftp_pkt_getuint32(pkt);
153 lo = sftp_pkt_getuint32(pkt);
154 ret.size = uint64_make(hi, lo);
155 }
156 if (ret.flags & SSH_FILEXFER_ATTR_UIDGID) {
157 ret.uid = sftp_pkt_getuint32(pkt);
158 ret.gid = sftp_pkt_getuint32(pkt);
159 }
160 if (ret.flags & SSH_FILEXFER_ATTR_PERMISSIONS) {
161 ret.permissions = sftp_pkt_getuint32(pkt);
162 }
163 if (ret.flags & SSH_FILEXFER_ATTR_ACMODTIME) {
164 ret.atime = sftp_pkt_getuint32(pkt);
165 ret.mtime = sftp_pkt_getuint32(pkt);
166 }
167 if (ret.flags & SSH_FILEXFER_ATTR_EXTENDED) {
168 int count;
169 count = sftp_pkt_getuint32(pkt);
170 while (count--) {
171 char *str;
172 int len;
173 /*
174 * We should try to analyse these, if we ever find one
175 * we recognise.
176 */
177 sftp_pkt_getstring(pkt, &str, &len);
178 sftp_pkt_getstring(pkt, &str, &len);
179 }
180 }
181 return ret;
182 }
183 static void sftp_pkt_free(struct sftp_packet *pkt)
184 {
185 if (pkt->data)
186 sfree(pkt->data);
187 sfree(pkt);
188 }
189
190 /* ----------------------------------------------------------------------
191 * Send and receive packet functions.
192 */
193 int sftp_send(struct sftp_packet *pkt)
194 {
195 int ret;
196 char x[4];
197 PUT_32BIT(x, pkt->length);
198 ret = (sftp_senddata(x, 4) && sftp_senddata(pkt->data, pkt->length));
199 sftp_pkt_free(pkt);
200 return ret;
201 }
202 struct sftp_packet *sftp_recv(void)
203 {
204 struct sftp_packet *pkt;
205 char x[4];
206
207 if (!sftp_recvdata(x, 4))
208 return NULL;
209
210 pkt = smalloc(sizeof(struct sftp_packet));
211 pkt->savedpos = 0;
212 pkt->length = pkt->maxlen = GET_32BIT(x);
213 pkt->data = smalloc(pkt->length);
214
215 if (!sftp_recvdata(pkt->data, pkt->length)) {
216 sftp_pkt_free(pkt);
217 return NULL;
218 }
219
220 pkt->type = sftp_pkt_getbyte(pkt);
221
222 return pkt;
223 }
224
225 /* ----------------------------------------------------------------------
226 * String handling routines.
227 */
228
229 static char *mkstr(char *s, int len)
230 {
231 char *p = smalloc(len + 1);
232 memcpy(p, s, len);
233 p[len] = '\0';
234 return p;
235 }
236
237 /* ----------------------------------------------------------------------
238 * SFTP primitives.
239 */
240
241 /*
242 * Deal with (and free) an FXP_STATUS packet. Return 1 if
243 * SSH_FX_OK, 0 if SSH_FX_EOF, and -1 for anything else (error).
244 * Also place the status into fxp_errtype.
245 */
246 static int fxp_got_status(struct sftp_packet *pktin)
247 {
248 static const char *const messages[] = {
249 /* SSH_FX_OK. The only time we will display a _message_ for this
250 * is if we were expecting something other than FXP_STATUS on
251 * success, so this is actually an error message! */
252 "unexpected OK response",
253 "end of file",
254 "no such file or directory",
255 "permission denied",
256 "failure",
257 "bad message",
258 "no connection",
259 "connection lost",
260 "operation unsupported",
261 };
262
263 if (pktin->type != SSH_FXP_STATUS) {
264 fxp_error_message = "expected FXP_STATUS packet";
265 fxp_errtype = -1;
266 } else {
267 fxp_errtype = sftp_pkt_getuint32(pktin);
268 if (fxp_errtype < 0 ||
269 fxp_errtype >= sizeof(messages) / sizeof(*messages))
270 fxp_error_message = "unknown error code";
271 else
272 fxp_error_message = messages[fxp_errtype];
273 }
274
275 if (fxp_errtype == SSH_FX_OK)
276 return 1;
277 else if (fxp_errtype == SSH_FX_EOF)
278 return 0;
279 else
280 return -1;
281 }
282
283 static void fxp_internal_error(char *msg)
284 {
285 fxp_error_message = msg;
286 fxp_errtype = -1;
287 }
288
289 const char *fxp_error(void)
290 {
291 return fxp_error_message;
292 }
293
294 int fxp_error_type(void)
295 {
296 return fxp_errtype;
297 }
298
299 /*
300 * Perform exchange of init/version packets. Return 0 on failure.
301 */
302 int fxp_init(void)
303 {
304 struct sftp_packet *pktout, *pktin;
305 int remotever;
306
307 pktout = sftp_pkt_init(SSH_FXP_INIT);
308 sftp_pkt_adduint32(pktout, SFTP_PROTO_VERSION);
309 sftp_send(pktout);
310
311 pktin = sftp_recv();
312 if (!pktin) {
313 fxp_internal_error("could not connect");
314 return 0;
315 }
316 if (pktin->type != SSH_FXP_VERSION) {
317 fxp_internal_error("did not receive FXP_VERSION");
318 return 0;
319 }
320 remotever = sftp_pkt_getuint32(pktin);
321 if (remotever > SFTP_PROTO_VERSION) {
322 fxp_internal_error
323 ("remote protocol is more advanced than we support");
324 return 0;
325 }
326 /*
327 * In principle, this packet might also contain extension-
328 * string pairs. We should work through them and look for any
329 * we recognise. In practice we don't currently do so because
330 * we know we don't recognise _any_.
331 */
332 sftp_pkt_free(pktin);
333
334 return 1;
335 }
336
337 /*
338 * Canonify a pathname.
339 */
340 char *fxp_realpath(char *path)
341 {
342 struct sftp_packet *pktin, *pktout;
343 int id;
344
345 pktout = sftp_pkt_init(SSH_FXP_REALPATH);
346 sftp_pkt_adduint32(pktout, 0x123); /* request id */
347 sftp_pkt_addstring_start(pktout);
348 sftp_pkt_addstring_str(pktout, path);
349 sftp_send(pktout);
350 pktin = sftp_recv();
351 id = sftp_pkt_getuint32(pktin);
352 if (id != 0x123) {
353 fxp_internal_error("request ID mismatch\n");
354 return NULL;
355 }
356 if (pktin->type == SSH_FXP_NAME) {
357 int count;
358 char *path;
359 int len;
360
361 count = sftp_pkt_getuint32(pktin);
362 if (count != 1) {
363 fxp_internal_error("REALPATH returned name count != 1\n");
364 return NULL;
365 }
366 sftp_pkt_getstring(pktin, &path, &len);
367 if (!path) {
368 fxp_internal_error("REALPATH returned malformed FXP_NAME\n");
369 return NULL;
370 }
371 path = mkstr(path, len);
372 sftp_pkt_free(pktin);
373 return path;
374 } else {
375 fxp_got_status(pktin);
376 return NULL;
377 }
378 }
379
380 /*
381 * Open a file.
382 */
383 struct fxp_handle *fxp_open(char *path, int type)
384 {
385 struct sftp_packet *pktin, *pktout;
386 int id;
387
388 pktout = sftp_pkt_init(SSH_FXP_OPEN);
389 sftp_pkt_adduint32(pktout, 0x567); /* request id */
390 sftp_pkt_addstring(pktout, path);
391 sftp_pkt_adduint32(pktout, type);
392 sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */
393 sftp_send(pktout);
394 pktin = sftp_recv();
395 id = sftp_pkt_getuint32(pktin);
396 if (id != 0x567) {
397 fxp_internal_error("request ID mismatch\n");
398 return NULL;
399 }
400 if (pktin->type == SSH_FXP_HANDLE) {
401 char *hstring;
402 struct fxp_handle *handle;
403 int len;
404
405 sftp_pkt_getstring(pktin, &hstring, &len);
406 if (!hstring) {
407 fxp_internal_error("OPEN returned malformed FXP_HANDLE\n");
408 return NULL;
409 }
410 handle = smalloc(sizeof(struct fxp_handle));
411 handle->hstring = mkstr(hstring, len);
412 handle->hlen = len;
413 sftp_pkt_free(pktin);
414 return handle;
415 } else {
416 fxp_got_status(pktin);
417 return NULL;
418 }
419 }
420
421 /*
422 * Open a directory.
423 */
424 struct fxp_handle *fxp_opendir(char *path)
425 {
426 struct sftp_packet *pktin, *pktout;
427 int id;
428
429 pktout = sftp_pkt_init(SSH_FXP_OPENDIR);
430 sftp_pkt_adduint32(pktout, 0x456); /* request id */
431 sftp_pkt_addstring(pktout, path);
432 sftp_send(pktout);
433 pktin = sftp_recv();
434 id = sftp_pkt_getuint32(pktin);
435 if (id != 0x456) {
436 fxp_internal_error("request ID mismatch\n");
437 return NULL;
438 }
439 if (pktin->type == SSH_FXP_HANDLE) {
440 char *hstring;
441 struct fxp_handle *handle;
442 int len;
443
444 sftp_pkt_getstring(pktin, &hstring, &len);
445 if (!hstring) {
446 fxp_internal_error("OPENDIR returned malformed FXP_HANDLE\n");
447 return NULL;
448 }
449 handle = smalloc(sizeof(struct fxp_handle));
450 handle->hstring = mkstr(hstring, len);
451 handle->hlen = len;
452 sftp_pkt_free(pktin);
453 return handle;
454 } else {
455 fxp_got_status(pktin);
456 return NULL;
457 }
458 }
459
460 /*
461 * Close a file/dir.
462 */
463 void fxp_close(struct fxp_handle *handle)
464 {
465 struct sftp_packet *pktin, *pktout;
466 int id;
467
468 pktout = sftp_pkt_init(SSH_FXP_CLOSE);
469 sftp_pkt_adduint32(pktout, 0x789); /* request id */
470 sftp_pkt_addstring_start(pktout);
471 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
472 sftp_send(pktout);
473 pktin = sftp_recv();
474 id = sftp_pkt_getuint32(pktin);
475 if (id != 0x789) {
476 fxp_internal_error("request ID mismatch\n");
477 return;
478 }
479 fxp_got_status(pktin);
480 sfree(handle->hstring);
481 sfree(handle);
482 }
483
484 int fxp_mkdir(char *path)
485 {
486 struct sftp_packet *pktin, *pktout;
487 int id;
488
489 pktout = sftp_pkt_init(SSH_FXP_MKDIR);
490 sftp_pkt_adduint32(pktout, 0x234); /* request id */
491 sftp_pkt_addstring_start(pktout);
492 sftp_pkt_addstring_data(pktout, path, strlen(path));
493 sftp_pkt_adduint32(pktout, 0); /* (FIXME) empty ATTRS structure */
494 sftp_send(pktout);
495 pktin = sftp_recv();
496 id = sftp_pkt_getuint32(pktin);
497 if (id != 0x234) {
498 fxp_internal_error("request ID mismatch\n");
499 return 0;
500 }
501 id = fxp_got_status(pktin);
502 if (id != 1) {
503 return 0;
504 }
505 return 1;
506 }
507
508 int fxp_rmdir(char *path)
509 {
510 struct sftp_packet *pktin, *pktout;
511 int id;
512
513 pktout = sftp_pkt_init(SSH_FXP_RMDIR);
514 sftp_pkt_adduint32(pktout, 0x345); /* request id */
515 sftp_pkt_addstring_start(pktout);
516 sftp_pkt_addstring_data(pktout, path, strlen(path));
517 sftp_send(pktout);
518 pktin = sftp_recv();
519 id = sftp_pkt_getuint32(pktin);
520 if (id != 0x345) {
521 fxp_internal_error("request ID mismatch\n");
522 return 0;
523 }
524 id = fxp_got_status(pktin);
525 if (id != 1) {
526 return 0;
527 }
528 return 1;
529 }
530
531 int fxp_rm(char *fname)
532 {
533 struct sftp_packet *pktin, *pktout;
534 int id;
535
536 pktout = sftp_pkt_init(SSH_FXP_REMOVE);
537 sftp_pkt_adduint32(pktout, 0x678); /* request id */
538 sftp_pkt_addstring_start(pktout);
539 sftp_pkt_addstring_data(pktout, fname, strlen(fname));
540 sftp_send(pktout);
541 pktin = sftp_recv();
542 id = sftp_pkt_getuint32(pktin);
543 if (id != 0x678) {
544 fxp_internal_error("request ID mismatch\n");
545 return 0;
546 }
547 id = fxp_got_status(pktin);
548 if (id != 1) {
549 return 0;
550 }
551 return 1;
552 }
553
554 /*
555 * Read from a file. Returns the number of bytes read, or -1 on an
556 * error, or possibly 0 if EOF. (I'm not entirely sure whether it
557 * will return 0 on EOF, or return -1 and store SSH_FX_EOF in the
558 * error indicator. It might even depend on the SFTP server.)
559 */
560 int fxp_read(struct fxp_handle *handle, char *buffer, uint64 offset,
561 int len)
562 {
563 struct sftp_packet *pktin, *pktout;
564 int id;
565
566 pktout = sftp_pkt_init(SSH_FXP_READ);
567 sftp_pkt_adduint32(pktout, 0xBCD); /* request id */
568 sftp_pkt_addstring_start(pktout);
569 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
570 sftp_pkt_adduint64(pktout, offset);
571 sftp_pkt_adduint32(pktout, len);
572 sftp_send(pktout);
573 pktin = sftp_recv();
574 id = sftp_pkt_getuint32(pktin);
575 if (id != 0xBCD) {
576 fxp_internal_error("request ID mismatch");
577 return -1;
578 }
579 if (pktin->type == SSH_FXP_DATA) {
580 char *str;
581 int rlen;
582
583 sftp_pkt_getstring(pktin, &str, &rlen);
584
585 if (rlen > len || rlen < 0) {
586 fxp_internal_error("READ returned more bytes than requested");
587 return -1;
588 }
589
590 memcpy(buffer, str, rlen);
591 sfree(pktin);
592 return rlen;
593 } else {
594 fxp_got_status(pktin);
595 return -1;
596 }
597 }
598
599 /*
600 * Read from a directory.
601 */
602 struct fxp_names *fxp_readdir(struct fxp_handle *handle)
603 {
604 struct sftp_packet *pktin, *pktout;
605 int id;
606
607 pktout = sftp_pkt_init(SSH_FXP_READDIR);
608 sftp_pkt_adduint32(pktout, 0xABC); /* request id */
609 sftp_pkt_addstring_start(pktout);
610 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
611 sftp_send(pktout);
612 pktin = sftp_recv();
613 id = sftp_pkt_getuint32(pktin);
614 if (id != 0xABC) {
615 fxp_internal_error("request ID mismatch\n");
616 return NULL;
617 }
618 if (pktin->type == SSH_FXP_NAME) {
619 struct fxp_names *ret;
620 int i;
621 ret = smalloc(sizeof(struct fxp_names));
622 ret->nnames = sftp_pkt_getuint32(pktin);
623 ret->names = smalloc(ret->nnames * sizeof(struct fxp_name));
624 for (i = 0; i < ret->nnames; i++) {
625 char *str;
626 int len;
627 sftp_pkt_getstring(pktin, &str, &len);
628 ret->names[i].filename = mkstr(str, len);
629 sftp_pkt_getstring(pktin, &str, &len);
630 ret->names[i].longname = mkstr(str, len);
631 ret->names[i].attrs = sftp_pkt_getattrs(pktin);
632 }
633 return ret;
634 } else {
635 fxp_got_status(pktin);
636 return NULL;
637 }
638 }
639
640 /*
641 * Write to a file. Returns 0 on error, 1 on OK.
642 */
643 int fxp_write(struct fxp_handle *handle, char *buffer, uint64 offset,
644 int len)
645 {
646 struct sftp_packet *pktin, *pktout;
647 int id;
648
649 pktout = sftp_pkt_init(SSH_FXP_WRITE);
650 sftp_pkt_adduint32(pktout, 0xDCB); /* request id */
651 sftp_pkt_addstring_start(pktout);
652 sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
653 sftp_pkt_adduint64(pktout, offset);
654 sftp_pkt_addstring_start(pktout);
655 sftp_pkt_addstring_data(pktout, buffer, len);
656 sftp_send(pktout);
657 pktin = sftp_recv();
658 id = sftp_pkt_getuint32(pktin);
659 if (id != 0xDCB) {
660 fxp_internal_error("request ID mismatch\n");
661 return 0;
662 }
663 fxp_got_status(pktin);
664 return fxp_errtype == SSH_FX_OK;
665 }
666
667 /*
668 * Free up an fxp_names structure.
669 */
670 void fxp_free_names(struct fxp_names *names)
671 {
672 int i;
673
674 for (i = 0; i < names->nnames; i++) {
675 sfree(names->names[i].filename);
676 sfree(names->names[i].longname);
677 }
678 sfree(names->names);
679 sfree(names);
680 }