More upgrades to psftp: it now supports mv, chmod, reget and reput.
[u/mdw/putty] / psftp.c
... / ...
CommitLineData
1/*
2 * psftp.c: front end for PSFTP.
3 */
4
5#include <windows.h>
6
7#include <stdio.h>
8#include <stdlib.h>
9#include <stdarg.h>
10#include <assert.h>
11#include <limits.h>
12
13#define PUTTY_DO_GLOBALS
14#include "putty.h"
15#include "storage.h"
16#include "ssh.h"
17#include "sftp.h"
18#include "int64.h"
19
20/*
21 * Since SFTP is a request-response oriented protocol, it requires
22 * no buffer management: when we send data, we stop and wait for an
23 * acknowledgement _anyway_, and so we can't possibly overfill our
24 * send buffer.
25 */
26
27/* ----------------------------------------------------------------------
28 * String handling routines.
29 */
30
31char *dupstr(char *s)
32{
33 int len = strlen(s);
34 char *p = smalloc(len + 1);
35 strcpy(p, s);
36 return p;
37}
38
39/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
40char *dupcat(char *s1, ...)
41{
42 int len;
43 char *p, *q, *sn;
44 va_list ap;
45
46 len = strlen(s1);
47 va_start(ap, s1);
48 while (1) {
49 sn = va_arg(ap, char *);
50 if (!sn)
51 break;
52 len += strlen(sn);
53 }
54 va_end(ap);
55
56 p = smalloc(len + 1);
57 strcpy(p, s1);
58 q = p + strlen(p);
59
60 va_start(ap, s1);
61 while (1) {
62 sn = va_arg(ap, char *);
63 if (!sn)
64 break;
65 strcpy(q, sn);
66 q += strlen(q);
67 }
68 va_end(ap);
69
70 return p;
71}
72
73/* ----------------------------------------------------------------------
74 * sftp client state.
75 */
76
77char *pwd, *homedir;
78
79/* ----------------------------------------------------------------------
80 * Higher-level helper functions used in commands.
81 */
82
83/*
84 * Attempt to canonify a pathname starting from the pwd. If
85 * canonification fails, at least fall back to returning a _valid_
86 * pathname (though it may be ugly, eg /home/simon/../foobar).
87 */
88char *canonify(char *name)
89{
90 char *fullname, *canonname;
91
92 if (name[0] == '/') {
93 fullname = dupstr(name);
94 } else {
95 char *slash;
96 if (pwd[strlen(pwd) - 1] == '/')
97 slash = "";
98 else
99 slash = "/";
100 fullname = dupcat(pwd, slash, name, NULL);
101 }
102
103 canonname = fxp_realpath(fullname);
104
105 if (canonname) {
106 sfree(fullname);
107 return canonname;
108 } else {
109 /*
110 * Attempt number 2. Some FXP_REALPATH implementations
111 * (glibc-based ones, in particular) require the _whole_
112 * path to point to something that exists, whereas others
113 * (BSD-based) only require all but the last component to
114 * exist. So if the first call failed, we should strip off
115 * everything from the last slash onwards and try again,
116 * then put the final component back on.
117 *
118 * Special cases:
119 *
120 * - if the last component is "/." or "/..", then we don't
121 * bother trying this because there's no way it can work.
122 *
123 * - if the thing actually ends with a "/", we remove it
124 * before we start. Except if the string is "/" itself
125 * (although I can't see why we'd have got here if so,
126 * because surely "/" would have worked the first
127 * time?), in which case we don't bother.
128 *
129 * - if there's no slash in the string at all, give up in
130 * confusion (we expect at least one because of the way
131 * we constructed the string).
132 */
133
134 int i;
135 char *returnname;
136
137 i = strlen(fullname);
138 if (i > 2 && fullname[i - 1] == '/')
139 fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
140 while (i > 0 && fullname[--i] != '/');
141
142 /*
143 * Give up on special cases.
144 */
145 if (fullname[i] != '/' || /* no slash at all */
146 !strcmp(fullname + i, "/.") || /* ends in /. */
147 !strcmp(fullname + i, "/..") || /* ends in /.. */
148 !strcmp(fullname, "/")) {
149 return fullname;
150 }
151
152 /*
153 * Now i points at the slash. Deal with the final special
154 * case i==0 (ie the whole path was "/nonexistentfile").
155 */
156 fullname[i] = '\0'; /* separate the string */
157 if (i == 0) {
158 canonname = fxp_realpath("/");
159 } else {
160 canonname = fxp_realpath(fullname);
161 }
162
163 if (!canonname)
164 return fullname; /* even that failed; give up */
165
166 /*
167 * We have a canonical name for all but the last path
168 * component. Concatenate the last component and return.
169 */
170 returnname = dupcat(canonname,
171 canonname[strlen(canonname) - 1] ==
172 '/' ? "" : "/", fullname + i + 1, NULL);
173 sfree(fullname);
174 sfree(canonname);
175 return returnname;
176 }
177}
178
179/* ----------------------------------------------------------------------
180 * Actual sftp commands.
181 */
182struct sftp_command {
183 char **words;
184 int nwords, wordssize;
185 int (*obey) (struct sftp_command *); /* returns <0 to quit */
186};
187
188int sftp_cmd_null(struct sftp_command *cmd)
189{
190 return 0;
191}
192
193int sftp_cmd_unknown(struct sftp_command *cmd)
194{
195 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
196 return 0;
197}
198
199int sftp_cmd_quit(struct sftp_command *cmd)
200{
201 return -1;
202}
203
204/*
205 * List a directory. If no arguments are given, list pwd; otherwise
206 * list the directory given in words[1].
207 */
208static int sftp_ls_compare(const void *av, const void *bv)
209{
210 const struct fxp_name *a = (const struct fxp_name *) av;
211 const struct fxp_name *b = (const struct fxp_name *) bv;
212 return strcmp(a->filename, b->filename);
213}
214int sftp_cmd_ls(struct sftp_command *cmd)
215{
216 struct fxp_handle *dirh;
217 struct fxp_names *names;
218 struct fxp_name *ournames;
219 int nnames, namesize;
220 char *dir, *cdir;
221 int i;
222
223 if (cmd->nwords < 2)
224 dir = ".";
225 else
226 dir = cmd->words[1];
227
228 cdir = canonify(dir);
229 if (!cdir) {
230 printf("%s: %s\n", dir, fxp_error());
231 return 0;
232 }
233
234 printf("Listing directory %s\n", cdir);
235
236 dirh = fxp_opendir(cdir);
237 if (dirh == NULL) {
238 printf("Unable to open %s: %s\n", dir, fxp_error());
239 } else {
240 nnames = namesize = 0;
241 ournames = NULL;
242
243 while (1) {
244
245 names = fxp_readdir(dirh);
246 if (names == NULL) {
247 if (fxp_error_type() == SSH_FX_EOF)
248 break;
249 printf("Reading directory %s: %s\n", dir, fxp_error());
250 break;
251 }
252 if (names->nnames == 0) {
253 fxp_free_names(names);
254 break;
255 }
256
257 if (nnames + names->nnames >= namesize) {
258 namesize += names->nnames + 128;
259 ournames =
260 srealloc(ournames, namesize * sizeof(*ournames));
261 }
262
263 for (i = 0; i < names->nnames; i++)
264 ournames[nnames++] = names->names[i];
265
266 names->nnames = 0; /* prevent free_names */
267 fxp_free_names(names);
268 }
269 fxp_close(dirh);
270
271 /*
272 * Now we have our filenames. Sort them by actual file
273 * name, and then output the longname parts.
274 */
275 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
276
277 /*
278 * And print them.
279 */
280 for (i = 0; i < nnames; i++)
281 printf("%s\n", ournames[i].longname);
282 }
283
284 sfree(cdir);
285
286 return 0;
287}
288
289/*
290 * Change directories. We do this by canonifying the new name, then
291 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
292 */
293int sftp_cmd_cd(struct sftp_command *cmd)
294{
295 struct fxp_handle *dirh;
296 char *dir;
297
298 if (cmd->nwords < 2)
299 dir = dupstr(homedir);
300 else
301 dir = canonify(cmd->words[1]);
302
303 if (!dir) {
304 printf("%s: %s\n", dir, fxp_error());
305 return 0;
306 }
307
308 dirh = fxp_opendir(dir);
309 if (!dirh) {
310 printf("Directory %s: %s\n", dir, fxp_error());
311 sfree(dir);
312 return 0;
313 }
314
315 fxp_close(dirh);
316
317 sfree(pwd);
318 pwd = dir;
319 printf("Remote directory is now %s\n", pwd);
320
321 return 0;
322}
323
324/*
325 * Get a file and save it at the local end. We have two very
326 * similar commands here: `get' and `reget', which differ in that
327 * `reget' checks for the existence of the destination file and
328 * starts from where a previous aborted transfer left off.
329 */
330int sftp_general_get(struct sftp_command *cmd, int restart)
331{
332 struct fxp_handle *fh;
333 char *fname, *outfname;
334 uint64 offset;
335 FILE *fp;
336
337 if (cmd->nwords < 2) {
338 printf("get: expects a filename\n");
339 return 0;
340 }
341
342 fname = canonify(cmd->words[1]);
343 if (!fname) {
344 printf("%s: %s\n", cmd->words[1], fxp_error());
345 return 0;
346 }
347 outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
348
349 fh = fxp_open(fname, SSH_FXF_READ);
350 if (!fh) {
351 printf("%s: %s\n", fname, fxp_error());
352 sfree(fname);
353 return 0;
354 }
355
356 if (restart) {
357 fp = fopen(outfname, "rb+");
358 } else {
359 fp = fopen(outfname, "wb");
360 }
361
362 if (!fp) {
363 printf("local: unable to open %s\n", outfname);
364 fxp_close(fh);
365 sfree(fname);
366 return 0;
367 }
368
369 if (restart) {
370 long posn;
371 fseek(fp, 0L, SEEK_END);
372 posn = ftell(fp);
373 printf("reget: restarting at file position %ld\n", posn);
374 offset = uint64_make(0, posn);
375 } else {
376 offset = uint64_make(0, 0);
377 }
378
379 printf("remote:%s => local:%s\n", fname, outfname);
380
381 /*
382 * FIXME: we can use FXP_FSTAT here to get the file size, and
383 * thus put up a progress bar.
384 */
385 while (1) {
386 char buffer[4096];
387 int len;
388 int wpos, wlen;
389
390 len = fxp_read(fh, buffer, offset, sizeof(buffer));
391 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
392 break;
393 if (len == -1) {
394 printf("error while reading: %s\n", fxp_error());
395 break;
396 }
397
398 wpos = 0;
399 while (wpos < len) {
400 wlen = fwrite(buffer, 1, len - wpos, fp);
401 if (wlen <= 0) {
402 printf("error while writing local file\n");
403 break;
404 }
405 wpos += wlen;
406 }
407 if (wpos < len) /* we had an error */
408 break;
409 offset = uint64_add32(offset, len);
410 }
411
412 fclose(fp);
413 fxp_close(fh);
414 sfree(fname);
415
416 return 0;
417}
418int sftp_cmd_get(struct sftp_command *cmd)
419{
420 return sftp_general_get(cmd, 0);
421}
422int sftp_cmd_reget(struct sftp_command *cmd)
423{
424 return sftp_general_get(cmd, 1);
425}
426
427/*
428 * Send a file and store it at the remote end. We have two very
429 * similar commands here: `put' and `reput', which differ in that
430 * `reput' checks for the existence of the destination file and
431 * starts from where a previous aborted transfer left off.
432 */
433int sftp_general_put(struct sftp_command *cmd, int restart)
434{
435 struct fxp_handle *fh;
436 char *fname, *origoutfname, *outfname;
437 uint64 offset;
438 FILE *fp;
439
440 if (cmd->nwords < 2) {
441 printf("put: expects a filename\n");
442 return 0;
443 }
444
445 fname = cmd->words[1];
446 origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
447 outfname = canonify(origoutfname);
448 if (!outfname) {
449 printf("%s: %s\n", origoutfname, fxp_error());
450 return 0;
451 }
452
453 fp = fopen(fname, "rb");
454 if (!fp) {
455 printf("local: unable to open %s\n", fname);
456 sfree(outfname);
457 return 0;
458 }
459 if (restart) {
460 fh = fxp_open(outfname,
461 SSH_FXF_WRITE);
462 } else {
463 fh = fxp_open(outfname,
464 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
465 }
466 if (!fh) {
467 printf("%s: %s\n", outfname, fxp_error());
468 sfree(outfname);
469 return 0;
470 }
471
472 if (restart) {
473 char decbuf[30];
474 struct fxp_attrs attrs;
475 if (!fxp_fstat(fh, &attrs)) {
476 printf("read size of %s: %s\n", outfname, fxp_error());
477 sfree(outfname);
478 return 0;
479 }
480 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
481 printf("read size of %s: size was not given\n", outfname);
482 sfree(outfname);
483 return 0;
484 }
485 offset = attrs.size;
486 uint64_decimal(offset, decbuf);
487 printf("reput: restarting at file position %s\n", decbuf);
488 if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
489 printf("reput: remote file is larger than we can deal with\n");
490 sfree(outfname);
491 return 0;
492 }
493 if (fseek(fp, offset.lo, SEEK_SET) != 0)
494 fseek(fp, 0, SEEK_END); /* *shrug* */
495 } else {
496 offset = uint64_make(0, 0);
497 }
498
499 printf("local:%s => remote:%s\n", fname, outfname);
500
501 /*
502 * FIXME: we can use FXP_FSTAT here to get the file size, and
503 * thus put up a progress bar.
504 */
505 while (1) {
506 char buffer[4096];
507 int len;
508
509 len = fread(buffer, 1, sizeof(buffer), fp);
510 if (len == -1) {
511 printf("error while reading local file\n");
512 break;
513 } else if (len == 0) {
514 break;
515 }
516 if (!fxp_write(fh, buffer, offset, len)) {
517 printf("error while writing: %s\n", fxp_error());
518 break;
519 }
520 offset = uint64_add32(offset, len);
521 }
522
523 fxp_close(fh);
524 fclose(fp);
525 sfree(outfname);
526
527 return 0;
528}
529int sftp_cmd_put(struct sftp_command *cmd)
530{
531 return sftp_general_put(cmd, 0);
532}
533int sftp_cmd_reput(struct sftp_command *cmd)
534{
535 return sftp_general_put(cmd, 1);
536}
537
538int sftp_cmd_mkdir(struct sftp_command *cmd)
539{
540 char *dir;
541 int result;
542
543
544 if (cmd->nwords < 2) {
545 printf("mkdir: expects a directory\n");
546 return 0;
547 }
548
549 dir = canonify(cmd->words[1]);
550 if (!dir) {
551 printf("%s: %s\n", dir, fxp_error());
552 return 0;
553 }
554
555 result = fxp_mkdir(dir);
556 if (!result) {
557 printf("mkdir %s: %s\n", dir, fxp_error());
558 sfree(dir);
559 return 0;
560 }
561
562 sfree(dir);
563 return 0;
564}
565
566int sftp_cmd_rmdir(struct sftp_command *cmd)
567{
568 char *dir;
569 int result;
570
571
572 if (cmd->nwords < 2) {
573 printf("rmdir: expects a directory\n");
574 return 0;
575 }
576
577 dir = canonify(cmd->words[1]);
578 if (!dir) {
579 printf("%s: %s\n", dir, fxp_error());
580 return 0;
581 }
582
583 result = fxp_rmdir(dir);
584 if (!result) {
585 printf("rmdir %s: %s\n", dir, fxp_error());
586 sfree(dir);
587 return 0;
588 }
589
590 sfree(dir);
591 return 0;
592}
593
594int sftp_cmd_rm(struct sftp_command *cmd)
595{
596 char *fname;
597 int result;
598
599 if (cmd->nwords < 2) {
600 printf("rm: expects a filename\n");
601 return 0;
602 }
603
604 fname = canonify(cmd->words[1]);
605 if (!fname) {
606 printf("%s: %s\n", fname, fxp_error());
607 return 0;
608 }
609
610 result = fxp_remove(fname);
611 if (!result) {
612 printf("rm %s: %s\n", fname, fxp_error());
613 sfree(fname);
614 return 0;
615 }
616
617 sfree(fname);
618 return 0;
619
620}
621
622int sftp_cmd_mv(struct sftp_command *cmd)
623{
624 char *srcfname, *dstfname;
625 int result;
626
627 if (cmd->nwords < 3) {
628 printf("mv: expects two filenames\n");
629 return 0;
630 }
631 srcfname = canonify(cmd->words[1]);
632 if (!srcfname) {
633 printf("%s: %s\n", srcfname, fxp_error());
634 return 0;
635 }
636
637 dstfname = canonify(cmd->words[2]);
638 if (!dstfname) {
639 printf("%s: %s\n", dstfname, fxp_error());
640 return 0;
641 }
642
643 result = fxp_rename(srcfname, dstfname);
644 if (!result) {
645 char const *error = fxp_error();
646 struct fxp_attrs attrs;
647
648 /*
649 * The move might have failed because dstfname pointed at a
650 * directory. We check this possibility now: if dstfname
651 * _is_ a directory, we re-attempt the move by appending
652 * the basename of srcfname to dstfname.
653 */
654 result = fxp_stat(dstfname, &attrs);
655 if (result &&
656 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
657 (attrs.permissions & 0040000)) {
658 char *p;
659 char *newname, *newcanon;
660 printf("(destination %s is a directory)\n", dstfname);
661 p = srcfname + strlen(srcfname);
662 while (p > srcfname && p[-1] != '/') p--;
663 newname = dupcat(dstfname, "/", p, NULL);
664 newcanon = canonify(newname);
665 sfree(newname);
666 if (newcanon) {
667 sfree(dstfname);
668 dstfname = newcanon;
669 result = fxp_rename(srcfname, dstfname);
670 error = result ? NULL : fxp_error();
671 }
672 }
673 if (error) {
674 printf("mv %s %s: %s\n", srcfname, dstfname, error);
675 sfree(srcfname);
676 sfree(dstfname);
677 return 0;
678 }
679 }
680 printf("%s -> %s\n", srcfname, dstfname);
681
682 sfree(srcfname);
683 sfree(dstfname);
684 return 0;
685}
686
687int sftp_cmd_chmod(struct sftp_command *cmd)
688{
689 char *fname, *mode;
690 int result;
691 struct fxp_attrs attrs;
692 unsigned attrs_clr, attrs_xor, oldperms, newperms;
693
694 if (cmd->nwords < 3) {
695 printf("chmod: expects a mode specifier and a filename\n");
696 return 0;
697 }
698
699 /*
700 * Attempt to parse the mode specifier in cmd->words[1]. We
701 * don't support the full horror of Unix chmod; instead we
702 * support a much simpler syntax in which the user can either
703 * specify an octal number, or a comma-separated sequence of
704 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
705 * _only_ be omitted if the only attribute mentioned is t,
706 * since all others require a user/group/other specification.
707 * Additionally, the s attribute may not be specified for any
708 * [ugoa] specifications other than exactly u or exactly g.
709 */
710 attrs_clr = attrs_xor = 0;
711 mode = cmd->words[1];
712 if (mode[0] >= '0' && mode[0] <= '9') {
713 if (mode[strspn(mode, "01234567")]) {
714 printf("chmod: numeric file modes should"
715 " contain digits 0-7 only\n");
716 return 0;
717 }
718 attrs_clr = 07777;
719 sscanf(mode, "%o", &attrs_xor);
720 attrs_xor &= attrs_clr;
721 } else {
722 while (*mode) {
723 char *modebegin = mode;
724 unsigned subset, perms;
725 int action;
726
727 subset = 0;
728 while (*mode && *mode != ',' &&
729 *mode != '+' && *mode != '-' && *mode != '=') {
730 switch (*mode) {
731 case 'u': subset |= 04700; break; /* setuid, user perms */
732 case 'g': subset |= 02070; break; /* setgid, group perms */
733 case 'o': subset |= 00007; break; /* just other perms */
734 case 'a': subset |= 06777; break; /* all of the above */
735 default:
736 printf("chmod: file mode '%.*s' contains unrecognised"
737 " user/group/other specifier '%c'\n",
738 strcspn(modebegin, ","), modebegin, *mode);
739 return 0;
740 }
741 mode++;
742 }
743 if (!*mode || *mode == ',') {
744 printf("chmod: file mode '%.*s' is incomplete\n",
745 strcspn(modebegin, ","), modebegin);
746 return 0;
747 }
748 action = *mode++;
749 if (!*mode || *mode == ',') {
750 printf("chmod: file mode '%.*s' is incomplete\n",
751 strcspn(modebegin, ","), modebegin);
752 return 0;
753 }
754 perms = 0;
755 while (*mode && *mode != ',') {
756 switch (*mode) {
757 case 'r': perms |= 00444; break;
758 case 'w': perms |= 00222; break;
759 case 'x': perms |= 00111; break;
760 case 't': perms |= 01000; subset |= 01000; break;
761 case 's':
762 if ((subset & 06777) != 04700 &&
763 (subset & 06777) != 02070) {
764 printf("chmod: file mode '%.*s': set[ug]id bit should"
765 " be used with exactly one of u or g only\n",
766 strcspn(modebegin, ","), modebegin);
767 return 0;
768 }
769 perms |= 06000;
770 break;
771 default:
772 printf("chmod: file mode '%.*s' contains unrecognised"
773 " permission specifier '%c'\n",
774 strcspn(modebegin, ","), modebegin, *mode);
775 return 0;
776 }
777 mode++;
778 }
779 if (!(subset & 06777) && (perms &~ subset)) {
780 printf("chmod: file mode '%.*s' contains no user/group/other"
781 " specifier and permissions other than 't' \n",
782 strcspn(modebegin, ","), modebegin, *mode);
783 return 0;
784 }
785 perms &= subset;
786 switch (action) {
787 case '+':
788 attrs_clr |= perms;
789 attrs_xor |= perms;
790 break;
791 case '-':
792 attrs_clr |= perms;
793 attrs_xor &= ~perms;
794 break;
795 case '=':
796 attrs_clr |= subset;
797 attrs_xor |= perms;
798 break;
799 }
800 if (*mode) mode++; /* eat comma */
801 }
802 }
803
804 fname = canonify(cmd->words[2]);
805 if (!fname) {
806 printf("%s: %s\n", fname, fxp_error());
807 return 0;
808 }
809
810 result = fxp_stat(fname, &attrs);
811 if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
812 printf("get attrs for %s: %s\n", fname,
813 result ? "file permissions not provided" : fxp_error());
814 sfree(fname);
815 return 0;
816 }
817
818 attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
819 oldperms = attrs.permissions & 07777;
820 attrs.permissions &= ~attrs_clr;
821 attrs.permissions ^= attrs_xor;
822 newperms = attrs.permissions & 07777;
823
824 result = fxp_setstat(fname, attrs);
825
826 if (!result) {
827 printf("set attrs for %s: %s\n", fname, fxp_error());
828 sfree(fname);
829 return 0;
830 }
831
832 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
833
834 sfree(fname);
835 return 0;
836}
837
838static struct sftp_cmd_lookup {
839 char *name;
840 int (*obey) (struct sftp_command *);
841} sftp_lookup[] = {
842 /*
843 * List of sftp commands. This is binary-searched so it MUST be
844 * in ASCII order.
845 */
846 {
847 "bye", sftp_cmd_quit}, {
848 "cd", sftp_cmd_cd}, {
849 "chmod", sftp_cmd_chmod}, {
850 "del", sftp_cmd_rm}, {
851 "delete", sftp_cmd_rm}, {
852 "dir", sftp_cmd_ls}, {
853 "exit", sftp_cmd_quit}, {
854 "get", sftp_cmd_get}, {
855 "ls", sftp_cmd_ls}, {
856 "mkdir", sftp_cmd_mkdir}, {
857 "mv", sftp_cmd_mv}, {
858 "put", sftp_cmd_put}, {
859 "quit", sftp_cmd_quit}, {
860 "reget", sftp_cmd_reget}, {
861 "ren", sftp_cmd_mv}, {
862 "rename", sftp_cmd_mv}, {
863 "reput", sftp_cmd_reput}, {
864 "rm", sftp_cmd_rm}, {
865 "rmdir", sftp_cmd_rmdir},};
866
867/* ----------------------------------------------------------------------
868 * Command line reading and parsing.
869 */
870struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
871{
872 char *line;
873 int linelen, linesize;
874 struct sftp_command *cmd;
875 char *p, *q, *r;
876 int quoting;
877
878 if ((mode == 0) || (modeflags & 1)) {
879 printf("psftp> ");
880 }
881 fflush(stdout);
882
883 cmd = smalloc(sizeof(struct sftp_command));
884 cmd->words = NULL;
885 cmd->nwords = 0;
886 cmd->wordssize = 0;
887
888 line = NULL;
889 linesize = linelen = 0;
890 while (1) {
891 int len;
892 char *ret;
893
894 linesize += 512;
895 line = srealloc(line, linesize);
896 ret = fgets(line + linelen, linesize - linelen, fp);
897 if (modeflags & 1) {
898 printf("%s", ret);
899 }
900
901 if (!ret || (linelen == 0 && line[0] == '\0')) {
902 cmd->obey = sftp_cmd_quit;
903 printf("quit\n");
904 return cmd; /* eof */
905 }
906 len = linelen + strlen(line + linelen);
907 linelen += len;
908 if (line[linelen - 1] == '\n') {
909 linelen--;
910 line[linelen] = '\0';
911 break;
912 }
913 }
914
915 /*
916 * Parse the command line into words. The syntax is:
917 * - double quotes are removed, but cause spaces within to be
918 * treated as non-separating.
919 * - a double-doublequote pair is a literal double quote, inside
920 * _or_ outside quotes. Like this:
921 *
922 * firstword "second word" "this has ""quotes"" in" sodoes""this""
923 *
924 * becomes
925 *
926 * >firstword<
927 * >second word<
928 * >this has "quotes" in<
929 * >sodoes"this"<
930 */
931 p = line;
932 while (*p) {
933 /* skip whitespace */
934 while (*p && (*p == ' ' || *p == '\t'))
935 p++;
936 /* mark start of word */
937 q = r = p; /* q sits at start, r writes word */
938 quoting = 0;
939 while (*p) {
940 if (!quoting && (*p == ' ' || *p == '\t'))
941 break; /* reached end of word */
942 else if (*p == '"' && p[1] == '"')
943 p += 2, *r++ = '"'; /* a literal quote */
944 else if (*p == '"')
945 p++, quoting = !quoting;
946 else
947 *r++ = *p++;
948 }
949 if (*p)
950 p++; /* skip over the whitespace */
951 *r = '\0';
952 if (cmd->nwords >= cmd->wordssize) {
953 cmd->wordssize = cmd->nwords + 16;
954 cmd->words =
955 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
956 }
957 cmd->words[cmd->nwords++] = q;
958 }
959
960 /*
961 * Now parse the first word and assign a function.
962 */
963
964 if (cmd->nwords == 0)
965 cmd->obey = sftp_cmd_null;
966 else {
967 int i, j, k, cmp;
968
969 cmd->obey = sftp_cmd_unknown;
970
971 i = -1;
972 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
973 while (j - i > 1) {
974 k = (j + i) / 2;
975 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
976 if (cmp < 0)
977 j = k;
978 else if (cmp > 0)
979 i = k;
980 else {
981 cmd->obey = sftp_lookup[k].obey;
982 break;
983 }
984 }
985 }
986
987 return cmd;
988}
989
990void do_sftp(int mode, int modeflags, char *batchfile)
991{
992 FILE *fp;
993
994 /*
995 * Do protocol initialisation.
996 */
997 if (!fxp_init()) {
998 fprintf(stderr,
999 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1000 return;
1001 }
1002
1003 /*
1004 * Find out where our home directory is.
1005 */
1006 homedir = fxp_realpath(".");
1007 if (!homedir) {
1008 fprintf(stderr,
1009 "Warning: failed to resolve home directory: %s\n",
1010 fxp_error());
1011 homedir = dupstr(".");
1012 } else {
1013 printf("Remote working directory is %s\n", homedir);
1014 }
1015 pwd = dupstr(homedir);
1016
1017 /*
1018 * Batch mode?
1019 */
1020 if (mode == 0) {
1021
1022 /* ------------------------------------------------------------------
1023 * Now we're ready to do Real Stuff.
1024 */
1025 while (1) {
1026 struct sftp_command *cmd;
1027 cmd = sftp_getcmd(stdin, 0, 0);
1028 if (!cmd)
1029 break;
1030 if (cmd->obey(cmd) < 0)
1031 break;
1032 }
1033 } else {
1034 fp = fopen(batchfile, "r");
1035 if (!fp) {
1036 printf("Fatal: unable to open %s\n", batchfile);
1037 return;
1038 }
1039 while (1) {
1040 struct sftp_command *cmd;
1041 cmd = sftp_getcmd(fp, mode, modeflags);
1042 if (!cmd)
1043 break;
1044 if (cmd->obey(cmd) < 0)
1045 break;
1046 if (fxp_error() != NULL) {
1047 if (!(modeflags & 2))
1048 break;
1049 }
1050 }
1051 fclose(fp);
1052
1053 }
1054}
1055
1056/* ----------------------------------------------------------------------
1057 * Dirty bits: integration with PuTTY.
1058 */
1059
1060static int verbose = 0;
1061
1062void verify_ssh_host_key(char *host, int port, char *keytype,
1063 char *keystr, char *fingerprint)
1064{
1065 int ret;
1066 HANDLE hin;
1067 DWORD savemode, i;
1068
1069 static const char absentmsg[] =
1070 "The server's host key is not cached in the registry. You\n"
1071 "have no guarantee that the server is the computer you\n"
1072 "think it is.\n"
1073 "The server's key fingerprint is:\n"
1074 "%s\n"
1075 "If you trust this host, enter \"y\" to add the key to\n"
1076 "PuTTY's cache and carry on connecting.\n"
1077 "If you want to carry on connecting just once, without\n"
1078 "adding the key to the cache, enter \"n\".\n"
1079 "If you do not trust this host, press Return to abandon the\n"
1080 "connection.\n"
1081 "Store key in cache? (y/n) ";
1082
1083 static const char wrongmsg[] =
1084 "WARNING - POTENTIAL SECURITY BREACH!\n"
1085 "The server's host key does not match the one PuTTY has\n"
1086 "cached in the registry. This means that either the\n"
1087 "server administrator has changed the host key, or you\n"
1088 "have actually connected to another computer pretending\n"
1089 "to be the server.\n"
1090 "The new key fingerprint is:\n"
1091 "%s\n"
1092 "If you were expecting this change and trust the new key,\n"
1093 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1094 "If you want to carry on connecting but without updating\n"
1095 "the cache, enter \"n\".\n"
1096 "If you want to abandon the connection completely, press\n"
1097 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1098 "safe choice.\n"
1099 "Update cached key? (y/n, Return cancels connection) ";
1100
1101 static const char abandoned[] = "Connection abandoned.\n";
1102
1103 char line[32];
1104
1105 /*
1106 * Verify the key against the registry.
1107 */
1108 ret = verify_host_key(host, port, keytype, keystr);
1109
1110 if (ret == 0) /* success - key matched OK */
1111 return;
1112
1113 if (ret == 2) { /* key was different */
1114 fprintf(stderr, wrongmsg, fingerprint);
1115 fflush(stderr);
1116 }
1117 if (ret == 1) { /* key was absent */
1118 fprintf(stderr, absentmsg, fingerprint);
1119 fflush(stderr);
1120 }
1121
1122 hin = GetStdHandle(STD_INPUT_HANDLE);
1123 GetConsoleMode(hin, &savemode);
1124 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1125 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1126 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1127 SetConsoleMode(hin, savemode);
1128
1129 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1130 if (line[0] == 'y' || line[0] == 'Y')
1131 store_host_key(host, port, keytype, keystr);
1132 } else {
1133 fprintf(stderr, abandoned);
1134 exit(0);
1135 }
1136}
1137
1138/*
1139 * Ask whether the selected cipher is acceptable (since it was
1140 * below the configured 'warn' threshold).
1141 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1142 */
1143void askcipher(char *ciphername, int cs)
1144{
1145 HANDLE hin;
1146 DWORD savemode, i;
1147
1148 static const char msg[] =
1149 "The first %scipher supported by the server is\n"
1150 "%s, which is below the configured warning threshold.\n"
1151 "Continue with connection? (y/n) ";
1152 static const char abandoned[] = "Connection abandoned.\n";
1153
1154 char line[32];
1155
1156 fprintf(stderr, msg,
1157 (cs == 0) ? "" :
1158 (cs == 1) ? "client-to-server " :
1159 "server-to-client ",
1160 ciphername);
1161 fflush(stderr);
1162
1163 hin = GetStdHandle(STD_INPUT_HANDLE);
1164 GetConsoleMode(hin, &savemode);
1165 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1166 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1167 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1168 SetConsoleMode(hin, savemode);
1169
1170 if (line[0] == 'y' || line[0] == 'Y') {
1171 return;
1172 } else {
1173 fprintf(stderr, abandoned);
1174 exit(0);
1175 }
1176}
1177
1178/*
1179 * Print an error message and perform a fatal exit.
1180 */
1181void fatalbox(char *fmt, ...)
1182{
1183 char str[0x100]; /* Make the size big enough */
1184 va_list ap;
1185 va_start(ap, fmt);
1186 strcpy(str, "Fatal:");
1187 vsprintf(str + strlen(str), fmt, ap);
1188 va_end(ap);
1189 strcat(str, "\n");
1190 fprintf(stderr, str);
1191
1192 exit(1);
1193}
1194void connection_fatal(char *fmt, ...)
1195{
1196 char str[0x100]; /* Make the size big enough */
1197 va_list ap;
1198 va_start(ap, fmt);
1199 strcpy(str, "Fatal:");
1200 vsprintf(str + strlen(str), fmt, ap);
1201 va_end(ap);
1202 strcat(str, "\n");
1203 fprintf(stderr, str);
1204
1205 exit(1);
1206}
1207
1208void logevent(char *string)
1209{
1210}
1211
1212void ldisc_send(char *buf, int len)
1213{
1214 /*
1215 * This is only here because of the calls to ldisc_send(NULL,
1216 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1217 * ldisc as an ldisc. So if we get called with any real data, I
1218 * want to know about it.
1219 */
1220 assert(len == 0);
1221}
1222
1223/*
1224 * Be told what socket we're supposed to be using.
1225 */
1226static SOCKET sftp_ssh_socket;
1227char *do_select(SOCKET skt, int startup)
1228{
1229 if (startup)
1230 sftp_ssh_socket = skt;
1231 else
1232 sftp_ssh_socket = INVALID_SOCKET;
1233 return NULL;
1234}
1235extern int select_result(WPARAM, LPARAM);
1236
1237/*
1238 * Receive a block of data from the SSH link. Block until all data
1239 * is available.
1240 *
1241 * To do this, we repeatedly call the SSH protocol module, with our
1242 * own trap in from_backend() to catch the data that comes back. We
1243 * do this until we have enough data.
1244 */
1245
1246static unsigned char *outptr; /* where to put the data */
1247static unsigned outlen; /* how much data required */
1248static unsigned char *pending = NULL; /* any spare data */
1249static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
1250int from_backend(int is_stderr, char *data, int datalen)
1251{
1252 unsigned char *p = (unsigned char *) data;
1253 unsigned len = (unsigned) datalen;
1254
1255 /*
1256 * stderr data is just spouted to local stderr and otherwise
1257 * ignored.
1258 */
1259 if (is_stderr) {
1260 fwrite(data, 1, len, stderr);
1261 return 0;
1262 }
1263
1264 /*
1265 * If this is before the real session begins, just return.
1266 */
1267 if (!outptr)
1268 return 0;
1269
1270 if (outlen > 0) {
1271 unsigned used = outlen;
1272 if (used > len)
1273 used = len;
1274 memcpy(outptr, p, used);
1275 outptr += used;
1276 outlen -= used;
1277 p += used;
1278 len -= used;
1279 }
1280
1281 if (len > 0) {
1282 if (pendsize < pendlen + len) {
1283 pendsize = pendlen + len + 4096;
1284 pending = (pending ? srealloc(pending, pendsize) :
1285 smalloc(pendsize));
1286 if (!pending)
1287 fatalbox("Out of memory");
1288 }
1289 memcpy(pending + pendlen, p, len);
1290 pendlen += len;
1291 }
1292
1293 return 0;
1294}
1295int sftp_recvdata(char *buf, int len)
1296{
1297 outptr = (unsigned char *) buf;
1298 outlen = len;
1299
1300 /*
1301 * See if the pending-input block contains some of what we
1302 * need.
1303 */
1304 if (pendlen > 0) {
1305 unsigned pendused = pendlen;
1306 if (pendused > outlen)
1307 pendused = outlen;
1308 memcpy(outptr, pending, pendused);
1309 memmove(pending, pending + pendused, pendlen - pendused);
1310 outptr += pendused;
1311 outlen -= pendused;
1312 pendlen -= pendused;
1313 if (pendlen == 0) {
1314 pendsize = 0;
1315 sfree(pending);
1316 pending = NULL;
1317 }
1318 if (outlen == 0)
1319 return 1;
1320 }
1321
1322 while (outlen > 0) {
1323 fd_set readfds;
1324
1325 FD_ZERO(&readfds);
1326 FD_SET(sftp_ssh_socket, &readfds);
1327 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1328 return 0; /* doom */
1329 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1330 }
1331
1332 return 1;
1333}
1334int sftp_senddata(char *buf, int len)
1335{
1336 back->send((unsigned char *) buf, len);
1337 return 1;
1338}
1339
1340/*
1341 * Loop through the ssh connection and authentication process.
1342 */
1343static void ssh_sftp_init(void)
1344{
1345 if (sftp_ssh_socket == INVALID_SOCKET)
1346 return;
1347 while (!back->sendok()) {
1348 fd_set readfds;
1349 FD_ZERO(&readfds);
1350 FD_SET(sftp_ssh_socket, &readfds);
1351 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1352 return; /* doom */
1353 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1354 }
1355}
1356
1357static char *password = NULL;
1358static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1359{
1360 HANDLE hin, hout;
1361 DWORD savemode, newmode, i;
1362
1363 if (password) {
1364 static int tried_once = 0;
1365
1366 if (tried_once) {
1367 return 0;
1368 } else {
1369 strncpy(str, password, maxlen);
1370 str[maxlen - 1] = '\0';
1371 tried_once = 1;
1372 return 1;
1373 }
1374 }
1375
1376 hin = GetStdHandle(STD_INPUT_HANDLE);
1377 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1378 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1379 fprintf(stderr, "Cannot get standard input/output handles\n");
1380 exit(1);
1381 }
1382
1383 GetConsoleMode(hin, &savemode);
1384 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1385 if (is_pw)
1386 newmode &= ~ENABLE_ECHO_INPUT;
1387 else
1388 newmode |= ENABLE_ECHO_INPUT;
1389 SetConsoleMode(hin, newmode);
1390
1391 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1392 ReadFile(hin, str, maxlen - 1, &i, NULL);
1393
1394 SetConsoleMode(hin, savemode);
1395
1396 if ((int) i > maxlen)
1397 i = maxlen - 1;
1398 else
1399 i = i - 2;
1400 str[i] = '\0';
1401
1402 if (is_pw)
1403 WriteFile(hout, "\r\n", 2, &i, NULL);
1404
1405 return 1;
1406}
1407
1408/*
1409 * Initialize the Win$ock driver.
1410 */
1411static void init_winsock(void)
1412{
1413 WORD winsock_ver;
1414 WSADATA wsadata;
1415
1416 winsock_ver = MAKEWORD(1, 1);
1417 if (WSAStartup(winsock_ver, &wsadata)) {
1418 fprintf(stderr, "Unable to initialise WinSock");
1419 exit(1);
1420 }
1421 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1422 fprintf(stderr, "WinSock version is incompatible with 1.1");
1423 exit(1);
1424 }
1425}
1426
1427/*
1428 * Short description of parameters.
1429 */
1430static void usage(void)
1431{
1432 printf("PuTTY Secure File Transfer (SFTP) client\n");
1433 printf("%s\n", ver);
1434 printf("Usage: psftp [options] user@host\n");
1435 printf("Options:\n");
1436 printf(" -b file use specified batchfile\n");
1437 printf(" -bc output batchfile commands\n");
1438 printf(" -be don't stop batchfile processing if errors\n");
1439 printf(" -v show verbose messages\n");
1440 printf(" -P port connect to specified port\n");
1441 printf(" -pw passw login with specified password\n");
1442 exit(1);
1443}
1444
1445/*
1446 * Main program. Parse arguments etc.
1447 */
1448int main(int argc, char *argv[])
1449{
1450 int i;
1451 int portnumber = 0;
1452 char *user, *host, *userhost, *realhost;
1453 char *err;
1454 int mode = 0;
1455 int modeflags = 0;
1456 char *batchfile = NULL;
1457
1458 flags = FLAG_STDERR;
1459 ssh_get_line = &get_line;
1460 init_winsock();
1461 sk_init();
1462
1463 userhost = user = NULL;
1464
1465 for (i = 1; i < argc; i++) {
1466 if (argv[i][0] != '-') {
1467 if (userhost)
1468 usage();
1469 else
1470 userhost = dupstr(argv[i]);
1471 } else if (strcmp(argv[i], "-v") == 0) {
1472 verbose = 1, flags |= FLAG_VERBOSE;
1473 } else if (strcmp(argv[i], "-h") == 0 ||
1474 strcmp(argv[i], "-?") == 0) {
1475 usage();
1476 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1477 user = argv[++i];
1478 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1479 portnumber = atoi(argv[++i]);
1480 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1481 password = argv[++i];
1482 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1483 mode = 1;
1484 batchfile = argv[++i];
1485 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1486 modeflags = modeflags | 1;
1487 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1488 modeflags = modeflags | 2;
1489 } else if (strcmp(argv[i], "--") == 0) {
1490 i++;
1491 break;
1492 } else {
1493 usage();
1494 }
1495 }
1496 argc -= i;
1497 argv += i;
1498 back = NULL;
1499
1500 if (argc > 0 || !userhost)
1501 usage();
1502
1503 /* Separate host and username */
1504 host = userhost;
1505 host = strrchr(host, '@');
1506 if (host == NULL) {
1507 host = userhost;
1508 } else {
1509 *host++ = '\0';
1510 if (user) {
1511 printf("psftp: multiple usernames specified; using \"%s\"\n",
1512 user);
1513 } else
1514 user = userhost;
1515 }
1516
1517 /* Try to load settings for this host */
1518 do_defaults(host, &cfg);
1519 if (cfg.host[0] == '\0') {
1520 /* No settings for this host; use defaults */
1521 do_defaults(NULL, &cfg);
1522 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1523 cfg.host[sizeof(cfg.host) - 1] = '\0';
1524 cfg.port = 22;
1525 }
1526
1527 /* Set username */
1528 if (user != NULL && user[0] != '\0') {
1529 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1530 cfg.username[sizeof(cfg.username) - 1] = '\0';
1531 }
1532 if (!cfg.username[0]) {
1533 printf("login as: ");
1534 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1535 fprintf(stderr, "psftp: aborting\n");
1536 exit(1);
1537 } else {
1538 int len = strlen(cfg.username);
1539 if (cfg.username[len - 1] == '\n')
1540 cfg.username[len - 1] = '\0';
1541 }
1542 }
1543
1544 if (cfg.protocol != PROT_SSH)
1545 cfg.port = 22;
1546
1547 if (portnumber)
1548 cfg.port = portnumber;
1549
1550 /* SFTP uses SSH2 by default always */
1551 cfg.sshprot = 2;
1552
1553 /* Set up subsystem name. FIXME: fudge for SSH1. */
1554 strcpy(cfg.remote_cmd, "sftp");
1555 cfg.ssh_subsys = TRUE;
1556 cfg.nopty = TRUE;
1557
1558 back = &ssh_backend;
1559
1560 err = back->init(cfg.host, cfg.port, &realhost);
1561 if (err != NULL) {
1562 fprintf(stderr, "ssh_init: %s", err);
1563 return 1;
1564 }
1565 ssh_sftp_init();
1566 if (verbose && realhost != NULL)
1567 printf("Connected to %s\n", realhost);
1568
1569 do_sftp(mode, modeflags, batchfile);
1570
1571 if (back != NULL && back->socket() != NULL) {
1572 char ch;
1573 back->special(TS_EOF);
1574 sftp_recvdata(&ch, 1);
1575 }
1576 WSACleanup();
1577 random_save_seed();
1578
1579 return 0;
1580}