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