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