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