Retired the #ifdef DUMP_PACKETS stuff in ssh.c because I'm utterly
[u/mdw/putty] / psftp.c
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 static int psftp_connect(char *userhost, char *user, int portnumber);
28 static void do_sftp_init(void);
29
30 /* ----------------------------------------------------------------------
31 * sftp client state.
32 */
33
34 char *pwd, *homedir;
35
36 /* ----------------------------------------------------------------------
37 * Higher-level helper functions used in commands.
38 */
39
40 /*
41 * Attempt to canonify a pathname starting from the pwd. If
42 * canonification fails, at least fall back to returning a _valid_
43 * pathname (though it may be ugly, eg /home/simon/../foobar).
44 */
45 char *canonify(char *name)
46 {
47 char *fullname, *canonname;
48
49 if (name[0] == '/') {
50 fullname = dupstr(name);
51 } else {
52 char *slash;
53 if (pwd[strlen(pwd) - 1] == '/')
54 slash = "";
55 else
56 slash = "/";
57 fullname = dupcat(pwd, slash, name, NULL);
58 }
59
60 canonname = fxp_realpath(fullname);
61
62 if (canonname) {
63 sfree(fullname);
64 return canonname;
65 } else {
66 /*
67 * Attempt number 2. Some FXP_REALPATH implementations
68 * (glibc-based ones, in particular) require the _whole_
69 * path to point to something that exists, whereas others
70 * (BSD-based) only require all but the last component to
71 * exist. So if the first call failed, we should strip off
72 * everything from the last slash onwards and try again,
73 * then put the final component back on.
74 *
75 * Special cases:
76 *
77 * - if the last component is "/." or "/..", then we don't
78 * bother trying this because there's no way it can work.
79 *
80 * - if the thing actually ends with a "/", we remove it
81 * before we start. Except if the string is "/" itself
82 * (although I can't see why we'd have got here if so,
83 * because surely "/" would have worked the first
84 * time?), in which case we don't bother.
85 *
86 * - if there's no slash in the string at all, give up in
87 * confusion (we expect at least one because of the way
88 * we constructed the string).
89 */
90
91 int i;
92 char *returnname;
93
94 i = strlen(fullname);
95 if (i > 2 && fullname[i - 1] == '/')
96 fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
97 while (i > 0 && fullname[--i] != '/');
98
99 /*
100 * Give up on special cases.
101 */
102 if (fullname[i] != '/' || /* no slash at all */
103 !strcmp(fullname + i, "/.") || /* ends in /. */
104 !strcmp(fullname + i, "/..") || /* ends in /.. */
105 !strcmp(fullname, "/")) {
106 return fullname;
107 }
108
109 /*
110 * Now i points at the slash. Deal with the final special
111 * case i==0 (ie the whole path was "/nonexistentfile").
112 */
113 fullname[i] = '\0'; /* separate the string */
114 if (i == 0) {
115 canonname = fxp_realpath("/");
116 } else {
117 canonname = fxp_realpath(fullname);
118 }
119
120 if (!canonname)
121 return fullname; /* even that failed; give up */
122
123 /*
124 * We have a canonical name for all but the last path
125 * component. Concatenate the last component and return.
126 */
127 returnname = dupcat(canonname,
128 canonname[strlen(canonname) - 1] ==
129 '/' ? "" : "/", fullname + i + 1, NULL);
130 sfree(fullname);
131 sfree(canonname);
132 return returnname;
133 }
134 }
135
136 /*
137 * Return a pointer to the portion of str that comes after the last
138 * slash (or backslash or colon, if `local' is TRUE).
139 */
140 static char *stripslashes(char *str, int local)
141 {
142 char *p;
143
144 if (local) {
145 p = strchr(str, ':');
146 if (p) str = p+1;
147 }
148
149 p = strrchr(str, '/');
150 if (p) str = p+1;
151
152 if (local) {
153 p = strrchr(str, '\\');
154 if (p) str = p+1;
155 }
156
157 return str;
158 }
159
160 /* ----------------------------------------------------------------------
161 * Actual sftp commands.
162 */
163 struct sftp_command {
164 char **words;
165 int nwords, wordssize;
166 int (*obey) (struct sftp_command *); /* returns <0 to quit */
167 };
168
169 int sftp_cmd_null(struct sftp_command *cmd)
170 {
171 return 1; /* success */
172 }
173
174 int sftp_cmd_unknown(struct sftp_command *cmd)
175 {
176 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
177 return 0; /* failure */
178 }
179
180 int sftp_cmd_quit(struct sftp_command *cmd)
181 {
182 return -1;
183 }
184
185 /*
186 * List a directory. If no arguments are given, list pwd; otherwise
187 * list the directory given in words[1].
188 */
189 static int sftp_ls_compare(const void *av, const void *bv)
190 {
191 const struct fxp_name *a = (const struct fxp_name *) av;
192 const struct fxp_name *b = (const struct fxp_name *) bv;
193 return strcmp(a->filename, b->filename);
194 }
195 int sftp_cmd_ls(struct sftp_command *cmd)
196 {
197 struct fxp_handle *dirh;
198 struct fxp_names *names;
199 struct fxp_name *ournames;
200 int nnames, namesize;
201 char *dir, *cdir;
202 int i;
203
204 if (back == NULL) {
205 printf("psftp: not connected to a host; use \"open host.name\"\n");
206 return 0;
207 }
208
209 if (cmd->nwords < 2)
210 dir = ".";
211 else
212 dir = cmd->words[1];
213
214 cdir = canonify(dir);
215 if (!cdir) {
216 printf("%s: %s\n", dir, fxp_error());
217 return 0;
218 }
219
220 printf("Listing directory %s\n", cdir);
221
222 dirh = fxp_opendir(cdir);
223 if (dirh == NULL) {
224 printf("Unable to open %s: %s\n", dir, fxp_error());
225 } else {
226 nnames = namesize = 0;
227 ournames = NULL;
228
229 while (1) {
230
231 names = fxp_readdir(dirh);
232 if (names == NULL) {
233 if (fxp_error_type() == SSH_FX_EOF)
234 break;
235 printf("Reading directory %s: %s\n", dir, fxp_error());
236 break;
237 }
238 if (names->nnames == 0) {
239 fxp_free_names(names);
240 break;
241 }
242
243 if (nnames + names->nnames >= namesize) {
244 namesize += names->nnames + 128;
245 ournames =
246 srealloc(ournames, namesize * sizeof(*ournames));
247 }
248
249 for (i = 0; i < names->nnames; i++)
250 ournames[nnames++] = names->names[i];
251
252 names->nnames = 0; /* prevent free_names */
253 fxp_free_names(names);
254 }
255 fxp_close(dirh);
256
257 /*
258 * Now we have our filenames. Sort them by actual file
259 * name, and then output the longname parts.
260 */
261 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
262
263 /*
264 * And print them.
265 */
266 for (i = 0; i < nnames; i++)
267 printf("%s\n", ournames[i].longname);
268 }
269
270 sfree(cdir);
271
272 return 1;
273 }
274
275 /*
276 * Change directories. We do this by canonifying the new name, then
277 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
278 */
279 int sftp_cmd_cd(struct sftp_command *cmd)
280 {
281 struct fxp_handle *dirh;
282 char *dir;
283
284 if (back == NULL) {
285 printf("psftp: not connected to a host; use \"open host.name\"\n");
286 return 0;
287 }
288
289 if (cmd->nwords < 2)
290 dir = dupstr(homedir);
291 else
292 dir = canonify(cmd->words[1]);
293
294 if (!dir) {
295 printf("%s: %s\n", dir, fxp_error());
296 return 0;
297 }
298
299 dirh = fxp_opendir(dir);
300 if (!dirh) {
301 printf("Directory %s: %s\n", dir, fxp_error());
302 sfree(dir);
303 return 0;
304 }
305
306 fxp_close(dirh);
307
308 sfree(pwd);
309 pwd = dir;
310 printf("Remote directory is now %s\n", pwd);
311
312 return 1;
313 }
314
315 /*
316 * Print current directory. Easy as pie.
317 */
318 int sftp_cmd_pwd(struct sftp_command *cmd)
319 {
320 if (back == NULL) {
321 printf("psftp: not connected to a host; use \"open host.name\"\n");
322 return 0;
323 }
324
325 printf("Remote directory is %s\n", pwd);
326 return 1;
327 }
328
329 /*
330 * Get a file and save it at the local end. We have two very
331 * similar commands here: `get' and `reget', which differ in that
332 * `reget' checks for the existence of the destination file and
333 * starts from where a previous aborted transfer left off.
334 */
335 int sftp_general_get(struct sftp_command *cmd, int restart)
336 {
337 struct fxp_handle *fh;
338 char *fname, *outfname;
339 uint64 offset;
340 FILE *fp;
341 int ret;
342
343 if (back == NULL) {
344 printf("psftp: not connected to a host; use \"open host.name\"\n");
345 return 0;
346 }
347
348 if (cmd->nwords < 2) {
349 printf("get: expects a filename\n");
350 return 0;
351 }
352
353 fname = canonify(cmd->words[1]);
354 if (!fname) {
355 printf("%s: %s\n", cmd->words[1], fxp_error());
356 return 0;
357 }
358 outfname = (cmd->nwords == 2 ?
359 stripslashes(cmd->words[1], 0) : cmd->words[2]);
360
361 fh = fxp_open(fname, SSH_FXF_READ);
362 if (!fh) {
363 printf("%s: %s\n", fname, fxp_error());
364 sfree(fname);
365 return 0;
366 }
367
368 if (restart) {
369 fp = fopen(outfname, "rb+");
370 } else {
371 fp = fopen(outfname, "wb");
372 }
373
374 if (!fp) {
375 printf("local: unable to open %s\n", outfname);
376 fxp_close(fh);
377 sfree(fname);
378 return 0;
379 }
380
381 if (restart) {
382 long posn;
383 fseek(fp, 0L, SEEK_END);
384 posn = ftell(fp);
385 printf("reget: restarting at file position %ld\n", posn);
386 offset = uint64_make(0, posn);
387 } else {
388 offset = uint64_make(0, 0);
389 }
390
391 printf("remote:%s => local:%s\n", fname, outfname);
392
393 /*
394 * FIXME: we can use FXP_FSTAT here to get the file size, and
395 * thus put up a progress bar.
396 */
397 ret = 1;
398 while (1) {
399 char buffer[4096];
400 int len;
401 int wpos, wlen;
402
403 len = fxp_read(fh, buffer, offset, sizeof(buffer));
404 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
405 break;
406 if (len == -1) {
407 printf("error while reading: %s\n", fxp_error());
408 ret = 0;
409 break;
410 }
411
412 wpos = 0;
413 while (wpos < len) {
414 wlen = fwrite(buffer, 1, len - wpos, fp);
415 if (wlen <= 0) {
416 printf("error while writing local file\n");
417 ret = 0;
418 break;
419 }
420 wpos += wlen;
421 }
422 if (wpos < len) { /* we had an error */
423 ret = 0;
424 break;
425 }
426 offset = uint64_add32(offset, len);
427 }
428
429 fclose(fp);
430 fxp_close(fh);
431 sfree(fname);
432
433 return ret;
434 }
435 int sftp_cmd_get(struct sftp_command *cmd)
436 {
437 return sftp_general_get(cmd, 0);
438 }
439 int sftp_cmd_reget(struct sftp_command *cmd)
440 {
441 return sftp_general_get(cmd, 1);
442 }
443
444 /*
445 * Send a file and store it at the remote end. We have two very
446 * similar commands here: `put' and `reput', which differ in that
447 * `reput' checks for the existence of the destination file and
448 * starts from where a previous aborted transfer left off.
449 */
450 int sftp_general_put(struct sftp_command *cmd, int restart)
451 {
452 struct fxp_handle *fh;
453 char *fname, *origoutfname, *outfname;
454 uint64 offset;
455 FILE *fp;
456 int ret;
457
458 if (back == NULL) {
459 printf("psftp: not connected to a host; use \"open host.name\"\n");
460 return 0;
461 }
462
463 if (cmd->nwords < 2) {
464 printf("put: expects a filename\n");
465 return 0;
466 }
467
468 fname = cmd->words[1];
469 origoutfname = (cmd->nwords == 2 ?
470 stripslashes(cmd->words[1], 1) : cmd->words[2]);
471 outfname = canonify(origoutfname);
472 if (!outfname) {
473 printf("%s: %s\n", origoutfname, fxp_error());
474 return 0;
475 }
476
477 fp = fopen(fname, "rb");
478 if (!fp) {
479 printf("local: unable to open %s\n", fname);
480 sfree(outfname);
481 return 0;
482 }
483 if (restart) {
484 fh = fxp_open(outfname,
485 SSH_FXF_WRITE);
486 } else {
487 fh = fxp_open(outfname,
488 SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
489 }
490 if (!fh) {
491 printf("%s: %s\n", outfname, fxp_error());
492 sfree(outfname);
493 return 0;
494 }
495
496 if (restart) {
497 char decbuf[30];
498 struct fxp_attrs attrs;
499 if (!fxp_fstat(fh, &attrs)) {
500 printf("read size of %s: %s\n", outfname, fxp_error());
501 sfree(outfname);
502 return 0;
503 }
504 if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
505 printf("read size of %s: size was not given\n", outfname);
506 sfree(outfname);
507 return 0;
508 }
509 offset = attrs.size;
510 uint64_decimal(offset, decbuf);
511 printf("reput: restarting at file position %s\n", decbuf);
512 if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
513 printf("reput: remote file is larger than we can deal with\n");
514 sfree(outfname);
515 return 0;
516 }
517 if (fseek(fp, offset.lo, SEEK_SET) != 0)
518 fseek(fp, 0, SEEK_END); /* *shrug* */
519 } else {
520 offset = uint64_make(0, 0);
521 }
522
523 printf("local:%s => remote:%s\n", fname, outfname);
524
525 /*
526 * FIXME: we can use FXP_FSTAT here to get the file size, and
527 * thus put up a progress bar.
528 */
529 ret = 1;
530 while (1) {
531 char buffer[4096];
532 int len;
533
534 len = fread(buffer, 1, sizeof(buffer), fp);
535 if (len == -1) {
536 printf("error while reading local file\n");
537 ret = 0;
538 break;
539 } else if (len == 0) {
540 break;
541 }
542 if (!fxp_write(fh, buffer, offset, len)) {
543 printf("error while writing: %s\n", fxp_error());
544 ret = 0;
545 break;
546 }
547 offset = uint64_add32(offset, len);
548 }
549
550 fxp_close(fh);
551 fclose(fp);
552 sfree(outfname);
553
554 return ret;
555 }
556 int sftp_cmd_put(struct sftp_command *cmd)
557 {
558 return sftp_general_put(cmd, 0);
559 }
560 int sftp_cmd_reput(struct sftp_command *cmd)
561 {
562 return sftp_general_put(cmd, 1);
563 }
564
565 int sftp_cmd_mkdir(struct sftp_command *cmd)
566 {
567 char *dir;
568 int result;
569
570 if (back == NULL) {
571 printf("psftp: not connected to a host; use \"open host.name\"\n");
572 return 0;
573 }
574
575 if (cmd->nwords < 2) {
576 printf("mkdir: expects a directory\n");
577 return 0;
578 }
579
580 dir = canonify(cmd->words[1]);
581 if (!dir) {
582 printf("%s: %s\n", dir, fxp_error());
583 return 0;
584 }
585
586 result = fxp_mkdir(dir);
587 if (!result) {
588 printf("mkdir %s: %s\n", dir, fxp_error());
589 sfree(dir);
590 return 0;
591 }
592
593 sfree(dir);
594 return 1;
595 }
596
597 int sftp_cmd_rmdir(struct sftp_command *cmd)
598 {
599 char *dir;
600 int result;
601
602 if (back == NULL) {
603 printf("psftp: not connected to a host; use \"open host.name\"\n");
604 return 0;
605 }
606
607 if (cmd->nwords < 2) {
608 printf("rmdir: expects a directory\n");
609 return 0;
610 }
611
612 dir = canonify(cmd->words[1]);
613 if (!dir) {
614 printf("%s: %s\n", dir, fxp_error());
615 return 0;
616 }
617
618 result = fxp_rmdir(dir);
619 if (!result) {
620 printf("rmdir %s: %s\n", dir, fxp_error());
621 sfree(dir);
622 return 0;
623 }
624
625 sfree(dir);
626 return 1;
627 }
628
629 int sftp_cmd_rm(struct sftp_command *cmd)
630 {
631 char *fname;
632 int result;
633
634 if (back == NULL) {
635 printf("psftp: not connected to a host; use \"open host.name\"\n");
636 return 0;
637 }
638
639 if (cmd->nwords < 2) {
640 printf("rm: expects a filename\n");
641 return 0;
642 }
643
644 fname = canonify(cmd->words[1]);
645 if (!fname) {
646 printf("%s: %s\n", fname, fxp_error());
647 return 0;
648 }
649
650 result = fxp_remove(fname);
651 if (!result) {
652 printf("rm %s: %s\n", fname, fxp_error());
653 sfree(fname);
654 return 0;
655 }
656
657 sfree(fname);
658 return 1;
659 }
660
661 int sftp_cmd_mv(struct sftp_command *cmd)
662 {
663 char *srcfname, *dstfname;
664 int result;
665
666 if (back == NULL) {
667 printf("psftp: not connected to a host; use \"open host.name\"\n");
668 return 0;
669 }
670
671 if (cmd->nwords < 3) {
672 printf("mv: expects two filenames\n");
673 return 0;
674 }
675 srcfname = canonify(cmd->words[1]);
676 if (!srcfname) {
677 printf("%s: %s\n", srcfname, fxp_error());
678 return 0;
679 }
680
681 dstfname = canonify(cmd->words[2]);
682 if (!dstfname) {
683 printf("%s: %s\n", dstfname, fxp_error());
684 return 0;
685 }
686
687 result = fxp_rename(srcfname, dstfname);
688 if (!result) {
689 char const *error = fxp_error();
690 struct fxp_attrs attrs;
691
692 /*
693 * The move might have failed because dstfname pointed at a
694 * directory. We check this possibility now: if dstfname
695 * _is_ a directory, we re-attempt the move by appending
696 * the basename of srcfname to dstfname.
697 */
698 result = fxp_stat(dstfname, &attrs);
699 if (result &&
700 (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
701 (attrs.permissions & 0040000)) {
702 char *p;
703 char *newname, *newcanon;
704 printf("(destination %s is a directory)\n", dstfname);
705 p = srcfname + strlen(srcfname);
706 while (p > srcfname && p[-1] != '/') p--;
707 newname = dupcat(dstfname, "/", p, NULL);
708 newcanon = canonify(newname);
709 sfree(newname);
710 if (newcanon) {
711 sfree(dstfname);
712 dstfname = newcanon;
713 result = fxp_rename(srcfname, dstfname);
714 error = result ? NULL : fxp_error();
715 }
716 }
717 if (error) {
718 printf("mv %s %s: %s\n", srcfname, dstfname, error);
719 sfree(srcfname);
720 sfree(dstfname);
721 return 0;
722 }
723 }
724 printf("%s -> %s\n", srcfname, dstfname);
725
726 sfree(srcfname);
727 sfree(dstfname);
728 return 1;
729 }
730
731 int sftp_cmd_chmod(struct sftp_command *cmd)
732 {
733 char *fname, *mode;
734 int result;
735 struct fxp_attrs attrs;
736 unsigned attrs_clr, attrs_xor, oldperms, newperms;
737
738 if (back == NULL) {
739 printf("psftp: not connected to a host; use \"open host.name\"\n");
740 return 0;
741 }
742
743 if (cmd->nwords < 3) {
744 printf("chmod: expects a mode specifier and a filename\n");
745 return 0;
746 }
747
748 /*
749 * Attempt to parse the mode specifier in cmd->words[1]. We
750 * don't support the full horror of Unix chmod; instead we
751 * support a much simpler syntax in which the user can either
752 * specify an octal number, or a comma-separated sequence of
753 * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
754 * _only_ be omitted if the only attribute mentioned is t,
755 * since all others require a user/group/other specification.
756 * Additionally, the s attribute may not be specified for any
757 * [ugoa] specifications other than exactly u or exactly g.
758 */
759 attrs_clr = attrs_xor = 0;
760 mode = cmd->words[1];
761 if (mode[0] >= '0' && mode[0] <= '9') {
762 if (mode[strspn(mode, "01234567")]) {
763 printf("chmod: numeric file modes should"
764 " contain digits 0-7 only\n");
765 return 0;
766 }
767 attrs_clr = 07777;
768 sscanf(mode, "%o", &attrs_xor);
769 attrs_xor &= attrs_clr;
770 } else {
771 while (*mode) {
772 char *modebegin = mode;
773 unsigned subset, perms;
774 int action;
775
776 subset = 0;
777 while (*mode && *mode != ',' &&
778 *mode != '+' && *mode != '-' && *mode != '=') {
779 switch (*mode) {
780 case 'u': subset |= 04700; break; /* setuid, user perms */
781 case 'g': subset |= 02070; break; /* setgid, group perms */
782 case 'o': subset |= 00007; break; /* just other perms */
783 case 'a': subset |= 06777; break; /* all of the above */
784 default:
785 printf("chmod: file mode '%.*s' contains unrecognised"
786 " user/group/other specifier '%c'\n",
787 strcspn(modebegin, ","), modebegin, *mode);
788 return 0;
789 }
790 mode++;
791 }
792 if (!*mode || *mode == ',') {
793 printf("chmod: file mode '%.*s' is incomplete\n",
794 strcspn(modebegin, ","), modebegin);
795 return 0;
796 }
797 action = *mode++;
798 if (!*mode || *mode == ',') {
799 printf("chmod: file mode '%.*s' is incomplete\n",
800 strcspn(modebegin, ","), modebegin);
801 return 0;
802 }
803 perms = 0;
804 while (*mode && *mode != ',') {
805 switch (*mode) {
806 case 'r': perms |= 00444; break;
807 case 'w': perms |= 00222; break;
808 case 'x': perms |= 00111; break;
809 case 't': perms |= 01000; subset |= 01000; break;
810 case 's':
811 if ((subset & 06777) != 04700 &&
812 (subset & 06777) != 02070) {
813 printf("chmod: file mode '%.*s': set[ug]id bit should"
814 " be used with exactly one of u or g only\n",
815 strcspn(modebegin, ","), modebegin);
816 return 0;
817 }
818 perms |= 06000;
819 break;
820 default:
821 printf("chmod: file mode '%.*s' contains unrecognised"
822 " permission specifier '%c'\n",
823 strcspn(modebegin, ","), modebegin, *mode);
824 return 0;
825 }
826 mode++;
827 }
828 if (!(subset & 06777) && (perms &~ subset)) {
829 printf("chmod: file mode '%.*s' contains no user/group/other"
830 " specifier and permissions other than 't' \n",
831 strcspn(modebegin, ","), modebegin, *mode);
832 return 0;
833 }
834 perms &= subset;
835 switch (action) {
836 case '+':
837 attrs_clr |= perms;
838 attrs_xor |= perms;
839 break;
840 case '-':
841 attrs_clr |= perms;
842 attrs_xor &= ~perms;
843 break;
844 case '=':
845 attrs_clr |= subset;
846 attrs_xor |= perms;
847 break;
848 }
849 if (*mode) mode++; /* eat comma */
850 }
851 }
852
853 fname = canonify(cmd->words[2]);
854 if (!fname) {
855 printf("%s: %s\n", fname, fxp_error());
856 return 0;
857 }
858
859 result = fxp_stat(fname, &attrs);
860 if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
861 printf("get attrs for %s: %s\n", fname,
862 result ? "file permissions not provided" : fxp_error());
863 sfree(fname);
864 return 0;
865 }
866
867 attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS; /* perms _only_ */
868 oldperms = attrs.permissions & 07777;
869 attrs.permissions &= ~attrs_clr;
870 attrs.permissions ^= attrs_xor;
871 newperms = attrs.permissions & 07777;
872
873 result = fxp_setstat(fname, attrs);
874
875 if (!result) {
876 printf("set attrs for %s: %s\n", fname, fxp_error());
877 sfree(fname);
878 return 0;
879 }
880
881 printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
882
883 sfree(fname);
884 return 1;
885 }
886
887 static int sftp_cmd_open(struct sftp_command *cmd)
888 {
889 if (back != NULL) {
890 printf("psftp: already connected\n");
891 return 0;
892 }
893
894 if (cmd->nwords < 2) {
895 printf("open: expects a host name\n");
896 return 0;
897 }
898
899 if (psftp_connect(cmd->words[1], NULL, 0)) {
900 back = NULL; /* connection is already closed */
901 return -1; /* this is fatal */
902 }
903 do_sftp_init();
904 return 1;
905 }
906
907 static int sftp_cmd_help(struct sftp_command *cmd);
908
909 static struct sftp_cmd_lookup {
910 char *name;
911 /*
912 * For help purposes, there are two kinds of command:
913 *
914 * - primary commands, in which `longhelp' is non-NULL. In
915 * this case `shorthelp' is descriptive text, and `longhelp'
916 * is longer descriptive text intended to be printed after
917 * the command name.
918 *
919 * - alias commands, in which `longhelp' is NULL. In this case
920 * `shorthelp' is the name of a primary command, which
921 * contains the help that should double up for this command.
922 */
923 char *shorthelp;
924 char *longhelp;
925 int (*obey) (struct sftp_command *);
926 } sftp_lookup[] = {
927 /*
928 * List of sftp commands. This is binary-searched so it MUST be
929 * in ASCII order.
930 */
931 {
932 "bye", "finish your SFTP session",
933 "\n"
934 " Terminates your SFTP session and quits the PSFTP program.\n",
935 sftp_cmd_quit
936 },
937 {
938 "cd", "change your remote working directory",
939 " [ <New working directory> ]\n"
940 " Change the remote working directory for your SFTP session.\n"
941 " If a new working directory is not supplied, you will be\n"
942 " returned to your home directory.\n",
943 sftp_cmd_cd
944 },
945 {
946 "chmod", "change file permissions and modes",
947 " ( <octal-digits> | <modifiers> ) <filename>\n"
948 " Change the file permissions on a file or directory.\n"
949 " <octal-digits> can be any octal Unix permission specifier.\n"
950 " Alternatively, <modifiers> can include:\n"
951 " u+r make file readable by owning user\n"
952 " u+w make file writable by owning user\n"
953 " u+x make file executable by owning user\n"
954 " u-r make file not readable by owning user\n"
955 " [also u-w, u-x]\n"
956 " g+r make file readable by members of owning group\n"
957 " [also g+w, g+x, g-r, g-w, g-x]\n"
958 " o+r make file readable by all other users\n"
959 " [also o+w, o+x, o-r, o-w, o-x]\n"
960 " a+r make file readable by absolutely everybody\n"
961 " [also a+w, a+x, a-r, a-w, a-x]\n"
962 " u+s enable the Unix set-user-ID bit\n"
963 " u-s disable the Unix set-user-ID bit\n"
964 " g+s enable the Unix set-group-ID bit\n"
965 " g-s disable the Unix set-group-ID bit\n"
966 " +t enable the Unix \"sticky bit\"\n"
967 " You can give more than one modifier for the same user (\"g-rwx\"), and\n"
968 " more than one user for the same modifier (\"ug+w\"). You can\n"
969 " use commas to separate different modifiers (\"u+rwx,g+s\").\n",
970 sftp_cmd_chmod
971 },
972 {
973 "del", "delete a file",
974 " <filename>\n"
975 " Delete a file.\n",
976 sftp_cmd_rm
977 },
978 {
979 "delete", "delete a file",
980 "\n"
981 " Delete a file.\n",
982 sftp_cmd_rm
983 },
984 {
985 "dir", "list contents of a remote directory",
986 " [ <directory-name> ]\n"
987 " List the contents of a specified directory on the server.\n"
988 " If <directory-name> is not given, the current working directory\n"
989 " will be listed.\n",
990 sftp_cmd_ls
991 },
992 {
993 "exit", "bye", NULL, sftp_cmd_quit
994 },
995 {
996 "get", "download a file from the server to your local machine",
997 " <filename> [ <local-filename> ]\n"
998 " Downloads a file on the server and stores it locally under\n"
999 " the same name, or under a different one if you supply the\n"
1000 " argument <local-filename>.\n",
1001 sftp_cmd_get
1002 },
1003 {
1004 "help", "give help",
1005 " [ <command> [ <command> ... ] ]\n"
1006 " Give general help if no commands are specified.\n"
1007 " If one or more commands are specified, give specific help on\n"
1008 " those particular commands.\n",
1009 sftp_cmd_help
1010 },
1011 {
1012 "ls", "dir", NULL,
1013 sftp_cmd_ls
1014 },
1015 {
1016 "mkdir", "create a directory on the remote server",
1017 " <directory-name>\n"
1018 " Creates a directory with the given name on the server.\n",
1019 sftp_cmd_mkdir
1020 },
1021 {
1022 "mv", "move or rename a file on the remote server",
1023 " <source-filename> <destination-filename>\n"
1024 " Moves or renames the file <source-filename> on the server,\n"
1025 " so that it is accessible under the name <destination-filename>.\n",
1026 sftp_cmd_mv
1027 },
1028 {
1029 "put", "upload a file from your local machine to the server",
1030 " <filename> [ <remote-filename> ]\n"
1031 " Uploads a file to the server and stores it there under\n"
1032 " the same name, or under a different one if you supply the\n"
1033 " argument <remote-filename>.\n",
1034 sftp_cmd_put
1035 },
1036 {
1037 "open", "connect to a host",
1038 " [<user>@]<hostname>\n"
1039 " Establishes an SFTP connection to a given host. Only usable\n"
1040 " when you did not already specify a host name on the command\n"
1041 " line.\n",
1042 sftp_cmd_open
1043 },
1044 {
1045 "pwd", "print your remote working directory",
1046 "\n"
1047 " Print the current remote working directory for your SFTP session.\n",
1048 sftp_cmd_pwd
1049 },
1050 {
1051 "quit", "bye", NULL,
1052 sftp_cmd_quit
1053 },
1054 {
1055 "reget", "continue downloading a file",
1056 " <filename> [ <local-filename> ]\n"
1057 " Works exactly like the \"get\" command, but the local file\n"
1058 " must already exist. The download will begin at the end of the\n"
1059 " file. This is for resuming a download that was interrupted.\n",
1060 sftp_cmd_reget
1061 },
1062 {
1063 "ren", "mv", NULL,
1064 sftp_cmd_mv
1065 },
1066 {
1067 "rename", "mv", NULL,
1068 sftp_cmd_mv
1069 },
1070 {
1071 "reput", "continue uploading a file",
1072 " <filename> [ <remote-filename> ]\n"
1073 " Works exactly like the \"put\" command, but the remote file\n"
1074 " must already exist. The upload will begin at the end of the\n"
1075 " file. This is for resuming an upload that was interrupted.\n",
1076 sftp_cmd_reput
1077 },
1078 {
1079 "rm", "del", NULL,
1080 sftp_cmd_rm
1081 },
1082 {
1083 "rmdir", "remove a directory on the remote server",
1084 " <directory-name>\n"
1085 " Removes the directory with the given name on the server.\n"
1086 " The directory will not be removed unless it is empty.\n",
1087 sftp_cmd_rmdir
1088 }
1089 };
1090
1091 const struct sftp_cmd_lookup *lookup_command(char *name)
1092 {
1093 int i, j, k, cmp;
1094
1095 i = -1;
1096 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
1097 while (j - i > 1) {
1098 k = (j + i) / 2;
1099 cmp = strcmp(name, sftp_lookup[k].name);
1100 if (cmp < 0)
1101 j = k;
1102 else if (cmp > 0)
1103 i = k;
1104 else {
1105 return &sftp_lookup[k];
1106 }
1107 }
1108 return NULL;
1109 }
1110
1111 static int sftp_cmd_help(struct sftp_command *cmd)
1112 {
1113 int i;
1114 if (cmd->nwords == 1) {
1115 /*
1116 * Give short help on each command.
1117 */
1118 int maxlen;
1119 maxlen = 0;
1120 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1121 int len = strlen(sftp_lookup[i].name);
1122 if (maxlen < len)
1123 maxlen = len;
1124 }
1125 for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1126 const struct sftp_cmd_lookup *lookup;
1127 lookup = &sftp_lookup[i];
1128 printf("%-*s", maxlen+2, lookup->name);
1129 if (lookup->longhelp == NULL)
1130 lookup = lookup_command(lookup->shorthelp);
1131 printf("%s\n", lookup->shorthelp);
1132 }
1133 } else {
1134 /*
1135 * Give long help on specific commands.
1136 */
1137 for (i = 1; i < cmd->nwords; i++) {
1138 const struct sftp_cmd_lookup *lookup;
1139 lookup = lookup_command(cmd->words[i]);
1140 if (!lookup) {
1141 printf("help: %s: command not found\n", cmd->words[i]);
1142 } else {
1143 printf("%s", lookup->name);
1144 if (lookup->longhelp == NULL)
1145 lookup = lookup_command(lookup->shorthelp);
1146 printf("%s", lookup->longhelp);
1147 }
1148 }
1149 }
1150 return 1;
1151 }
1152
1153 /* ----------------------------------------------------------------------
1154 * Command line reading and parsing.
1155 */
1156 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1157 {
1158 char *line;
1159 int linelen, linesize;
1160 struct sftp_command *cmd;
1161 char *p, *q, *r;
1162 int quoting;
1163
1164 if ((mode == 0) || (modeflags & 1)) {
1165 printf("psftp> ");
1166 }
1167 fflush(stdout);
1168
1169 cmd = smalloc(sizeof(struct sftp_command));
1170 cmd->words = NULL;
1171 cmd->nwords = 0;
1172 cmd->wordssize = 0;
1173
1174 line = NULL;
1175 linesize = linelen = 0;
1176 while (1) {
1177 int len;
1178 char *ret;
1179
1180 linesize += 512;
1181 line = srealloc(line, linesize);
1182 ret = fgets(line + linelen, linesize - linelen, fp);
1183
1184 if (!ret || (linelen == 0 && line[0] == '\0')) {
1185 cmd->obey = sftp_cmd_quit;
1186 if ((mode == 0) || (modeflags & 1))
1187 printf("quit\n");
1188 return cmd; /* eof */
1189 }
1190 len = linelen + strlen(line + linelen);
1191 linelen += len;
1192 if (line[linelen - 1] == '\n') {
1193 linelen--;
1194 line[linelen] = '\0';
1195 break;
1196 }
1197 }
1198 if (modeflags & 1) {
1199 printf("%s\n", line);
1200 }
1201
1202 /*
1203 * Parse the command line into words. The syntax is:
1204 * - double quotes are removed, but cause spaces within to be
1205 * treated as non-separating.
1206 * - a double-doublequote pair is a literal double quote, inside
1207 * _or_ outside quotes. Like this:
1208 *
1209 * firstword "second word" "this has ""quotes"" in" sodoes""this""
1210 *
1211 * becomes
1212 *
1213 * >firstword<
1214 * >second word<
1215 * >this has "quotes" in<
1216 * >sodoes"this"<
1217 */
1218 p = line;
1219 while (*p) {
1220 /* skip whitespace */
1221 while (*p && (*p == ' ' || *p == '\t'))
1222 p++;
1223 /* mark start of word */
1224 q = r = p; /* q sits at start, r writes word */
1225 quoting = 0;
1226 while (*p) {
1227 if (!quoting && (*p == ' ' || *p == '\t'))
1228 break; /* reached end of word */
1229 else if (*p == '"' && p[1] == '"')
1230 p += 2, *r++ = '"'; /* a literal quote */
1231 else if (*p == '"')
1232 p++, quoting = !quoting;
1233 else
1234 *r++ = *p++;
1235 }
1236 if (*p)
1237 p++; /* skip over the whitespace */
1238 *r = '\0';
1239 if (cmd->nwords >= cmd->wordssize) {
1240 cmd->wordssize = cmd->nwords + 16;
1241 cmd->words =
1242 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1243 }
1244 cmd->words[cmd->nwords++] = q;
1245 }
1246
1247 /*
1248 * Now parse the first word and assign a function.
1249 */
1250
1251 if (cmd->nwords == 0)
1252 cmd->obey = sftp_cmd_null;
1253 else {
1254 const struct sftp_cmd_lookup *lookup;
1255 lookup = lookup_command(cmd->words[0]);
1256 if (!lookup)
1257 cmd->obey = sftp_cmd_unknown;
1258 else
1259 cmd->obey = lookup->obey;
1260 }
1261
1262 return cmd;
1263 }
1264
1265 static void do_sftp_init(void)
1266 {
1267 /*
1268 * Do protocol initialisation.
1269 */
1270 if (!fxp_init()) {
1271 fprintf(stderr,
1272 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1273 return;
1274 }
1275
1276 /*
1277 * Find out where our home directory is.
1278 */
1279 homedir = fxp_realpath(".");
1280 if (!homedir) {
1281 fprintf(stderr,
1282 "Warning: failed to resolve home directory: %s\n",
1283 fxp_error());
1284 homedir = dupstr(".");
1285 } else {
1286 printf("Remote working directory is %s\n", homedir);
1287 }
1288 pwd = dupstr(homedir);
1289 }
1290
1291 void do_sftp(int mode, int modeflags, char *batchfile)
1292 {
1293 FILE *fp;
1294 int ret;
1295
1296 /*
1297 * Batch mode?
1298 */
1299 if (mode == 0) {
1300
1301 /* ------------------------------------------------------------------
1302 * Now we're ready to do Real Stuff.
1303 */
1304 while (1) {
1305 struct sftp_command *cmd;
1306 cmd = sftp_getcmd(stdin, 0, 0);
1307 if (!cmd)
1308 break;
1309 if (cmd->obey(cmd) < 0)
1310 break;
1311 }
1312 } else {
1313 fp = fopen(batchfile, "r");
1314 if (!fp) {
1315 printf("Fatal: unable to open %s\n", batchfile);
1316 return;
1317 }
1318 while (1) {
1319 struct sftp_command *cmd;
1320 cmd = sftp_getcmd(fp, mode, modeflags);
1321 if (!cmd)
1322 break;
1323 ret = cmd->obey(cmd);
1324 if (ret < 0)
1325 break;
1326 if (ret == 0) {
1327 if (!(modeflags & 2))
1328 break;
1329 }
1330 }
1331 fclose(fp);
1332
1333 }
1334 }
1335
1336 /* ----------------------------------------------------------------------
1337 * Dirty bits: integration with PuTTY.
1338 */
1339
1340 static int verbose = 0;
1341
1342 void verify_ssh_host_key(char *host, int port, char *keytype,
1343 char *keystr, char *fingerprint)
1344 {
1345 int ret;
1346 HANDLE hin;
1347 DWORD savemode, i;
1348
1349 static const char absentmsg[] =
1350 "The server's host key is not cached in the registry. You\n"
1351 "have no guarantee that the server is the computer you\n"
1352 "think it is.\n"
1353 "The server's key fingerprint is:\n"
1354 "%s\n"
1355 "If you trust this host, enter \"y\" to add the key to\n"
1356 "PuTTY's cache and carry on connecting.\n"
1357 "If you want to carry on connecting just once, without\n"
1358 "adding the key to the cache, enter \"n\".\n"
1359 "If you do not trust this host, press Return to abandon the\n"
1360 "connection.\n"
1361 "Store key in cache? (y/n) ";
1362
1363 static const char wrongmsg[] =
1364 "WARNING - POTENTIAL SECURITY BREACH!\n"
1365 "The server's host key does not match the one PuTTY has\n"
1366 "cached in the registry. This means that either the\n"
1367 "server administrator has changed the host key, or you\n"
1368 "have actually connected to another computer pretending\n"
1369 "to be the server.\n"
1370 "The new key fingerprint is:\n"
1371 "%s\n"
1372 "If you were expecting this change and trust the new key,\n"
1373 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1374 "If you want to carry on connecting but without updating\n"
1375 "the cache, enter \"n\".\n"
1376 "If you want to abandon the connection completely, press\n"
1377 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1378 "safe choice.\n"
1379 "Update cached key? (y/n, Return cancels connection) ";
1380
1381 static const char abandoned[] = "Connection abandoned.\n";
1382
1383 char line[32];
1384
1385 /*
1386 * Verify the key against the registry.
1387 */
1388 ret = verify_host_key(host, port, keytype, keystr);
1389
1390 if (ret == 0) /* success - key matched OK */
1391 return;
1392
1393 if (ret == 2) { /* key was different */
1394 fprintf(stderr, wrongmsg, fingerprint);
1395 fflush(stderr);
1396 }
1397 if (ret == 1) { /* key was absent */
1398 fprintf(stderr, absentmsg, fingerprint);
1399 fflush(stderr);
1400 }
1401
1402 hin = GetStdHandle(STD_INPUT_HANDLE);
1403 GetConsoleMode(hin, &savemode);
1404 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1405 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1406 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1407 SetConsoleMode(hin, savemode);
1408
1409 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1410 if (line[0] == 'y' || line[0] == 'Y')
1411 store_host_key(host, port, keytype, keystr);
1412 } else {
1413 fprintf(stderr, abandoned);
1414 exit(0);
1415 }
1416 }
1417
1418 /*
1419 * Ask whether the selected cipher is acceptable (since it was
1420 * below the configured 'warn' threshold).
1421 * cs: 0 = both ways, 1 = client->server, 2 = server->client
1422 */
1423 void askcipher(char *ciphername, int cs)
1424 {
1425 HANDLE hin;
1426 DWORD savemode, i;
1427
1428 static const char msg[] =
1429 "The first %scipher supported by the server is\n"
1430 "%s, which is below the configured warning threshold.\n"
1431 "Continue with connection? (y/n) ";
1432 static const char abandoned[] = "Connection abandoned.\n";
1433
1434 char line[32];
1435
1436 fprintf(stderr, msg,
1437 (cs == 0) ? "" :
1438 (cs == 1) ? "client-to-server " :
1439 "server-to-client ",
1440 ciphername);
1441 fflush(stderr);
1442
1443 hin = GetStdHandle(STD_INPUT_HANDLE);
1444 GetConsoleMode(hin, &savemode);
1445 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1446 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1447 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1448 SetConsoleMode(hin, savemode);
1449
1450 if (line[0] == 'y' || line[0] == 'Y') {
1451 return;
1452 } else {
1453 fprintf(stderr, abandoned);
1454 exit(0);
1455 }
1456 }
1457
1458 /*
1459 * Ask whether to wipe a session log file before writing to it.
1460 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
1461 */
1462 int askappend(char *filename)
1463 {
1464 HANDLE hin;
1465 DWORD savemode, i;
1466
1467 static const char msgtemplate[] =
1468 "The session log file \"%.*s\" already exists.\n"
1469 "You can overwrite it with a new session log,\n"
1470 "append your session log to the end of it,\n"
1471 "or disable session logging for this session.\n"
1472 "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
1473 "or just press Return to disable logging.\n"
1474 "Wipe the log file? (y/n, Return cancels logging) ";
1475
1476 char line[32];
1477
1478 fprintf(stderr, msgtemplate, FILENAME_MAX, filename);
1479 fflush(stderr);
1480
1481 hin = GetStdHandle(STD_INPUT_HANDLE);
1482 GetConsoleMode(hin, &savemode);
1483 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1484 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1485 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1486 SetConsoleMode(hin, savemode);
1487
1488 if (line[0] == 'y' || line[0] == 'Y')
1489 return 2;
1490 else if (line[0] == 'n' || line[0] == 'N')
1491 return 1;
1492 else
1493 return 0;
1494 }
1495
1496 /*
1497 * Warn about the obsolescent key file format.
1498 */
1499 void old_keyfile_warning(void)
1500 {
1501 static const char message[] =
1502 "You are loading an SSH 2 private key which has an\n"
1503 "old version of the file format. This means your key\n"
1504 "file is not fully tamperproof. Future versions of\n"
1505 "PuTTY may stop supporting this private key format,\n"
1506 "so we recommend you convert your key to the new\n"
1507 "format.\n"
1508 "\n"
1509 "Once the key is loaded into PuTTYgen, you can perform\n"
1510 "this conversion simply by saving it again.\n";
1511
1512 fputs(message, stderr);
1513 }
1514
1515 /*
1516 * Print an error message and perform a fatal exit.
1517 */
1518 void fatalbox(char *fmt, ...)
1519 {
1520 char str[0x100]; /* Make the size big enough */
1521 va_list ap;
1522 va_start(ap, fmt);
1523 strcpy(str, "Fatal:");
1524 vsprintf(str + strlen(str), fmt, ap);
1525 va_end(ap);
1526 strcat(str, "\n");
1527 fputs(stderr, str);
1528
1529 exit(1);
1530 }
1531 void connection_fatal(char *fmt, ...)
1532 {
1533 char str[0x100]; /* Make the size big enough */
1534 va_list ap;
1535 va_start(ap, fmt);
1536 strcpy(str, "Fatal:");
1537 vsprintf(str + strlen(str), fmt, ap);
1538 va_end(ap);
1539 strcat(str, "\n");
1540 fputs(stderr, str);
1541
1542 exit(1);
1543 }
1544
1545 void logevent(char *string)
1546 {
1547 }
1548
1549 void ldisc_send(char *buf, int len, int interactive)
1550 {
1551 /*
1552 * This is only here because of the calls to ldisc_send(NULL,
1553 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1554 * ldisc as an ldisc. So if we get called with any real data, I
1555 * want to know about it.
1556 */
1557 assert(len == 0);
1558 }
1559
1560 /*
1561 * Be told what socket we're supposed to be using.
1562 */
1563 static SOCKET sftp_ssh_socket;
1564 char *do_select(SOCKET skt, int startup)
1565 {
1566 if (startup)
1567 sftp_ssh_socket = skt;
1568 else
1569 sftp_ssh_socket = INVALID_SOCKET;
1570 return NULL;
1571 }
1572 extern int select_result(WPARAM, LPARAM);
1573
1574 /*
1575 * Receive a block of data from the SSH link. Block until all data
1576 * is available.
1577 *
1578 * To do this, we repeatedly call the SSH protocol module, with our
1579 * own trap in from_backend() to catch the data that comes back. We
1580 * do this until we have enough data.
1581 */
1582
1583 static unsigned char *outptr; /* where to put the data */
1584 static unsigned outlen; /* how much data required */
1585 static unsigned char *pending = NULL; /* any spare data */
1586 static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
1587 int from_backend(int is_stderr, char *data, int datalen)
1588 {
1589 unsigned char *p = (unsigned char *) data;
1590 unsigned len = (unsigned) datalen;
1591
1592 /*
1593 * stderr data is just spouted to local stderr and otherwise
1594 * ignored.
1595 */
1596 if (is_stderr) {
1597 fwrite(data, 1, len, stderr);
1598 return 0;
1599 }
1600
1601 /*
1602 * If this is before the real session begins, just return.
1603 */
1604 if (!outptr)
1605 return 0;
1606
1607 if (outlen > 0) {
1608 unsigned used = outlen;
1609 if (used > len)
1610 used = len;
1611 memcpy(outptr, p, used);
1612 outptr += used;
1613 outlen -= used;
1614 p += used;
1615 len -= used;
1616 }
1617
1618 if (len > 0) {
1619 if (pendsize < pendlen + len) {
1620 pendsize = pendlen + len + 4096;
1621 pending = (pending ? srealloc(pending, pendsize) :
1622 smalloc(pendsize));
1623 if (!pending)
1624 fatalbox("Out of memory");
1625 }
1626 memcpy(pending + pendlen, p, len);
1627 pendlen += len;
1628 }
1629
1630 return 0;
1631 }
1632 int sftp_recvdata(char *buf, int len)
1633 {
1634 outptr = (unsigned char *) buf;
1635 outlen = len;
1636
1637 /*
1638 * See if the pending-input block contains some of what we
1639 * need.
1640 */
1641 if (pendlen > 0) {
1642 unsigned pendused = pendlen;
1643 if (pendused > outlen)
1644 pendused = outlen;
1645 memcpy(outptr, pending, pendused);
1646 memmove(pending, pending + pendused, pendlen - pendused);
1647 outptr += pendused;
1648 outlen -= pendused;
1649 pendlen -= pendused;
1650 if (pendlen == 0) {
1651 pendsize = 0;
1652 sfree(pending);
1653 pending = NULL;
1654 }
1655 if (outlen == 0)
1656 return 1;
1657 }
1658
1659 while (outlen > 0) {
1660 fd_set readfds;
1661
1662 FD_ZERO(&readfds);
1663 FD_SET(sftp_ssh_socket, &readfds);
1664 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1665 return 0; /* doom */
1666 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1667 }
1668
1669 return 1;
1670 }
1671 int sftp_senddata(char *buf, int len)
1672 {
1673 back->send((unsigned char *) buf, len);
1674 return 1;
1675 }
1676
1677 /*
1678 * Loop through the ssh connection and authentication process.
1679 */
1680 static void ssh_sftp_init(void)
1681 {
1682 if (sftp_ssh_socket == INVALID_SOCKET)
1683 return;
1684 while (!back->sendok()) {
1685 fd_set readfds;
1686 FD_ZERO(&readfds);
1687 FD_SET(sftp_ssh_socket, &readfds);
1688 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1689 return; /* doom */
1690 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1691 }
1692 }
1693
1694 static char *password = NULL;
1695 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1696 {
1697 HANDLE hin, hout;
1698 DWORD savemode, newmode, i;
1699
1700 if (password) {
1701 static int tried_once = 0;
1702
1703 if (tried_once) {
1704 return 0;
1705 } else {
1706 strncpy(str, password, maxlen);
1707 str[maxlen - 1] = '\0';
1708 tried_once = 1;
1709 return 1;
1710 }
1711 }
1712
1713 hin = GetStdHandle(STD_INPUT_HANDLE);
1714 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1715 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1716 fprintf(stderr, "Cannot get standard input/output handles\n");
1717 exit(1);
1718 }
1719
1720 GetConsoleMode(hin, &savemode);
1721 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1722 if (is_pw)
1723 newmode &= ~ENABLE_ECHO_INPUT;
1724 else
1725 newmode |= ENABLE_ECHO_INPUT;
1726 SetConsoleMode(hin, newmode);
1727
1728 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1729 ReadFile(hin, str, maxlen - 1, &i, NULL);
1730
1731 SetConsoleMode(hin, savemode);
1732
1733 if ((int) i > maxlen)
1734 i = maxlen - 1;
1735 else
1736 i = i - 2;
1737 str[i] = '\0';
1738
1739 if (is_pw)
1740 WriteFile(hout, "\r\n", 2, &i, NULL);
1741
1742 return 1;
1743 }
1744
1745 /*
1746 * Initialize the Win$ock driver.
1747 */
1748 static void init_winsock(void)
1749 {
1750 WORD winsock_ver;
1751 WSADATA wsadata;
1752
1753 winsock_ver = MAKEWORD(1, 1);
1754 if (WSAStartup(winsock_ver, &wsadata)) {
1755 fprintf(stderr, "Unable to initialise WinSock");
1756 exit(1);
1757 }
1758 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1759 fprintf(stderr, "WinSock version is incompatible with 1.1");
1760 exit(1);
1761 }
1762 }
1763
1764 /*
1765 * Short description of parameters.
1766 */
1767 static void usage(void)
1768 {
1769 printf("PuTTY Secure File Transfer (SFTP) client\n");
1770 printf("%s\n", ver);
1771 printf("Usage: psftp [options] user@host\n");
1772 printf("Options:\n");
1773 printf(" -b file use specified batchfile\n");
1774 printf(" -bc output batchfile commands\n");
1775 printf(" -be don't stop batchfile processing if errors\n");
1776 printf(" -v show verbose messages\n");
1777 printf(" -P port connect to specified port\n");
1778 printf(" -pw passw login with specified password\n");
1779 exit(1);
1780 }
1781
1782 /*
1783 * Connect to a host.
1784 */
1785 static int psftp_connect(char *userhost, char *user, int portnumber)
1786 {
1787 char *host, *realhost;
1788 char *err;
1789
1790 /* Separate host and username */
1791 host = userhost;
1792 host = strrchr(host, '@');
1793 if (host == NULL) {
1794 host = userhost;
1795 } else {
1796 *host++ = '\0';
1797 if (user) {
1798 printf("psftp: multiple usernames specified; using \"%s\"\n",
1799 user);
1800 } else
1801 user = userhost;
1802 }
1803
1804 /* Try to load settings for this host */
1805 do_defaults(host, &cfg);
1806 if (cfg.host[0] == '\0') {
1807 /* No settings for this host; use defaults */
1808 do_defaults(NULL, &cfg);
1809 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1810 cfg.host[sizeof(cfg.host) - 1] = '\0';
1811 cfg.port = 22;
1812 }
1813
1814 /*
1815 * Trim leading whitespace off the hostname if it's there.
1816 */
1817 {
1818 int space = strspn(cfg.host, " \t");
1819 memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1820 }
1821
1822 /* See if host is of the form user@host */
1823 if (cfg.host[0] != '\0') {
1824 char *atsign = strchr(cfg.host, '@');
1825 /* Make sure we're not overflowing the user field */
1826 if (atsign) {
1827 if (atsign - cfg.host < sizeof cfg.username) {
1828 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1829 cfg.username[atsign - cfg.host] = '\0';
1830 }
1831 memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1832 }
1833 }
1834
1835 /*
1836 * Trim a colon suffix off the hostname if it's there.
1837 */
1838 cfg.host[strcspn(cfg.host, ":")] = '\0';
1839
1840 /* Set username */
1841 if (user != NULL && user[0] != '\0') {
1842 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1843 cfg.username[sizeof(cfg.username) - 1] = '\0';
1844 }
1845 if (!cfg.username[0]) {
1846 printf("login as: ");
1847 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1848 fprintf(stderr, "psftp: aborting\n");
1849 exit(1);
1850 } else {
1851 int len = strlen(cfg.username);
1852 if (cfg.username[len - 1] == '\n')
1853 cfg.username[len - 1] = '\0';
1854 }
1855 }
1856
1857 if (cfg.protocol != PROT_SSH)
1858 cfg.port = 22;
1859
1860 if (portnumber)
1861 cfg.port = portnumber;
1862
1863 /* SFTP uses SSH2 by default always */
1864 cfg.sshprot = 2;
1865
1866 /*
1867 * Disable scary things which shouldn't be enabled for simple
1868 * things like SCP and SFTP: agent forwarding, port forwarding,
1869 * X forwarding.
1870 */
1871 cfg.x11_forward = 0;
1872 cfg.agentfwd = 0;
1873 cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1874
1875 /* Set up subsystem name. */
1876 strcpy(cfg.remote_cmd, "sftp");
1877 cfg.ssh_subsys = TRUE;
1878 cfg.nopty = TRUE;
1879
1880 /*
1881 * Set up fallback option, for SSH1 servers or servers with the
1882 * sftp subsystem not enabled but the server binary installed
1883 * in the usual place. We only support fallback on Unix
1884 * systems, and we use a kludgy piece of shellery which should
1885 * try to find sftp-server in various places (the obvious
1886 * systemwide spots /usr/lib and /usr/local/lib, and then the
1887 * user's PATH) and finally give up.
1888 *
1889 * test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1890 * test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1891 * exec sftp-server
1892 *
1893 * the idea being that this will attempt to use either of the
1894 * obvious pathnames and then give up, and when it does give up
1895 * it will print the preferred pathname in the error messages.
1896 */
1897 cfg.remote_cmd_ptr2 =
1898 "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1899 "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1900 "exec sftp-server";
1901 cfg.ssh_subsys2 = FALSE;
1902
1903 back = &ssh_backend;
1904
1905 err = back->init(cfg.host, cfg.port, &realhost, 0);
1906 if (err != NULL) {
1907 fprintf(stderr, "ssh_init: %s\n", err);
1908 return 1;
1909 }
1910 ssh_sftp_init();
1911 if (verbose && realhost != NULL)
1912 printf("Connected to %s\n", realhost);
1913 return 0;
1914 }
1915
1916 /*
1917 * Main program. Parse arguments etc.
1918 */
1919 int main(int argc, char *argv[])
1920 {
1921 int i;
1922 int portnumber = 0;
1923 char *userhost, *user;
1924 int mode = 0;
1925 int modeflags = 0;
1926 char *batchfile = NULL;
1927
1928 flags = FLAG_STDERR | FLAG_INTERACTIVE;
1929 ssh_get_line = &get_line;
1930 init_winsock();
1931 sk_init();
1932
1933 userhost = user = NULL;
1934
1935 for (i = 1; i < argc; i++) {
1936 if (argv[i][0] != '-') {
1937 if (userhost)
1938 usage();
1939 else
1940 userhost = dupstr(argv[i]);
1941 } else if (strcmp(argv[i], "-v") == 0) {
1942 verbose = 1, flags |= FLAG_VERBOSE;
1943 } else if (strcmp(argv[i], "-h") == 0 ||
1944 strcmp(argv[i], "-?") == 0) {
1945 usage();
1946 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1947 user = argv[++i];
1948 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1949 portnumber = atoi(argv[++i]);
1950 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1951 password = argv[++i];
1952 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1953 mode = 1;
1954 batchfile = argv[++i];
1955 } else if (strcmp(argv[i], "-bc") == 0) {
1956 modeflags = modeflags | 1;
1957 } else if (strcmp(argv[i], "-be") == 0) {
1958 modeflags = modeflags | 2;
1959 } else if (strcmp(argv[i], "--") == 0) {
1960 i++;
1961 break;
1962 } else {
1963 usage();
1964 }
1965 }
1966 argc -= i;
1967 argv += i;
1968 back = NULL;
1969
1970 /*
1971 * If a user@host string has already been provided, connect to
1972 * it now.
1973 */
1974 if (userhost) {
1975 if (psftp_connect(userhost, user, portnumber))
1976 return 1;
1977 do_sftp_init();
1978 } else {
1979 printf("psftp: no hostname specified; use \"open host.name\""
1980 " to connect\n");
1981 }
1982
1983 do_sftp(mode, modeflags, batchfile);
1984
1985 if (back != NULL && back->socket() != NULL) {
1986 char ch;
1987 back->special(TS_EOF);
1988 sftp_recvdata(&ch, 1);
1989 }
1990 WSACleanup();
1991 random_save_seed();
1992
1993 return 0;
1994 }