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