D'oh, remove two rogue diagnostics
[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>
4c7f0d61 11
4a8fc3c4 12#define PUTTY_DO_GLOBALS
13#include "putty.h"
14#include "storage.h"
15#include "ssh.h"
4c7f0d61 16#include "sftp.h"
17#include "int64.h"
18
5471d09a 19/*
20 * Since SFTP is a request-response oriented protocol, it requires
21 * no buffer management: when we send data, we stop and wait for an
22 * acknowledgement _anyway_, and so we can't possibly overfill our
23 * send buffer.
24 */
25
4c7f0d61 26/* ----------------------------------------------------------------------
27 * String handling routines.
28 */
29
32874aea 30char *dupstr(char *s)
31{
4c7f0d61 32 int len = strlen(s);
32874aea 33 char *p = smalloc(len + 1);
4c7f0d61 34 strcpy(p, s);
35 return p;
36}
37
f9e162aa 38/* Allocate the concatenation of N strings. Terminate arg list with NULL. */
32874aea 39char *dupcat(char *s1, ...)
40{
f9e162aa 41 int len;
42 char *p, *q, *sn;
43 va_list ap;
44
45 len = strlen(s1);
46 va_start(ap, s1);
47 while (1) {
48 sn = va_arg(ap, char *);
49 if (!sn)
50 break;
51 len += strlen(sn);
52 }
53 va_end(ap);
54
32874aea 55 p = smalloc(len + 1);
f9e162aa 56 strcpy(p, s1);
57 q = p + strlen(p);
58
59 va_start(ap, s1);
60 while (1) {
61 sn = va_arg(ap, char *);
62 if (!sn)
63 break;
64 strcpy(q, sn);
65 q += strlen(q);
66 }
67 va_end(ap);
68
69 return p;
70}
71
4c7f0d61 72/* ----------------------------------------------------------------------
73 * sftp client state.
74 */
75
76char *pwd, *homedir;
77
78/* ----------------------------------------------------------------------
79 * Higher-level helper functions used in commands.
80 */
81
82/*
f9e162aa 83 * Attempt to canonify a pathname starting from the pwd. If
84 * canonification fails, at least fall back to returning a _valid_
85 * pathname (though it may be ugly, eg /home/simon/../foobar).
4c7f0d61 86 */
32874aea 87char *canonify(char *name)
88{
f9e162aa 89 char *fullname, *canonname;
4a8fc3c4 90
f9e162aa 91 if (name[0] == '/') {
92 fullname = dupstr(name);
93 } else {
4a8fc3c4 94 char *slash;
32874aea 95 if (pwd[strlen(pwd) - 1] == '/')
4a8fc3c4 96 slash = "";
97 else
98 slash = "/";
99 fullname = dupcat(pwd, slash, name, NULL);
f9e162aa 100 }
4a8fc3c4 101
102 canonname = fxp_realpath(fullname);
103
f9e162aa 104 if (canonname) {
105 sfree(fullname);
106 return canonname;
50d7e054 107 } else {
32874aea 108 /*
109 * Attempt number 2. Some FXP_REALPATH implementations
110 * (glibc-based ones, in particular) require the _whole_
111 * path to point to something that exists, whereas others
112 * (BSD-based) only require all but the last component to
113 * exist. So if the first call failed, we should strip off
114 * everything from the last slash onwards and try again,
115 * then put the final component back on.
116 *
117 * Special cases:
118 *
119 * - if the last component is "/." or "/..", then we don't
120 * bother trying this because there's no way it can work.
121 *
122 * - if the thing actually ends with a "/", we remove it
123 * before we start. Except if the string is "/" itself
124 * (although I can't see why we'd have got here if so,
125 * because surely "/" would have worked the first
126 * time?), in which case we don't bother.
127 *
128 * - if there's no slash in the string at all, give up in
129 * confusion (we expect at least one because of the way
130 * we constructed the string).
131 */
132
133 int i;
134 char *returnname;
135
136 i = strlen(fullname);
137 if (i > 2 && fullname[i - 1] == '/')
138 fullname[--i] = '\0'; /* strip trailing / unless at pos 0 */
139 while (i > 0 && fullname[--i] != '/');
140
141 /*
142 * Give up on special cases.
143 */
144 if (fullname[i] != '/' || /* no slash at all */
145 !strcmp(fullname + i, "/.") || /* ends in /. */
146 !strcmp(fullname + i, "/..") || /* ends in /.. */
147 !strcmp(fullname, "/")) {
148 return fullname;
149 }
150
151 /*
152 * Now i points at the slash. Deal with the final special
153 * case i==0 (ie the whole path was "/nonexistentfile").
154 */
155 fullname[i] = '\0'; /* separate the string */
156 if (i == 0) {
157 canonname = fxp_realpath("/");
158 } else {
159 canonname = fxp_realpath(fullname);
160 }
161
162 if (!canonname)
163 return fullname; /* even that failed; give up */
164
165 /*
166 * We have a canonical name for all but the last path
167 * component. Concatenate the last component and return.
168 */
169 returnname = dupcat(canonname,
170 canonname[strlen(canonname) - 1] ==
171 '/' ? "" : "/", fullname + i + 1, NULL);
172 sfree(fullname);
173 sfree(canonname);
174 return returnname;
50d7e054 175 }
4c7f0d61 176}
177
178/* ----------------------------------------------------------------------
179 * Actual sftp commands.
180 */
181struct sftp_command {
182 char **words;
183 int nwords, wordssize;
32874aea 184 int (*obey) (struct sftp_command *); /* returns <0 to quit */
4c7f0d61 185};
186
32874aea 187int sftp_cmd_null(struct sftp_command *cmd)
188{
4c7f0d61 189 return 0;
190}
191
32874aea 192int sftp_cmd_unknown(struct sftp_command *cmd)
193{
4c7f0d61 194 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
195 return 0;
196}
197
32874aea 198int sftp_cmd_quit(struct sftp_command *cmd)
199{
4c7f0d61 200 return -1;
201}
202
203/*
204 * List a directory. If no arguments are given, list pwd; otherwise
205 * list the directory given in words[1].
206 */
32874aea 207static int sftp_ls_compare(const void *av, const void *bv)
208{
209 const struct fxp_name *a = (const struct fxp_name *) av;
210 const struct fxp_name *b = (const struct fxp_name *) bv;
4c7f0d61 211 return strcmp(a->filename, b->filename);
212}
32874aea 213int sftp_cmd_ls(struct sftp_command *cmd)
214{
4c7f0d61 215 struct fxp_handle *dirh;
216 struct fxp_names *names;
217 struct fxp_name *ournames;
218 int nnames, namesize;
219 char *dir, *cdir;
220 int i;
221
222 if (cmd->nwords < 2)
223 dir = ".";
224 else
225 dir = cmd->words[1];
226
227 cdir = canonify(dir);
228 if (!cdir) {
229 printf("%s: %s\n", dir, fxp_error());
230 return 0;
231 }
232
233 printf("Listing directory %s\n", cdir);
234
235 dirh = fxp_opendir(cdir);
236 if (dirh == NULL) {
237 printf("Unable to open %s: %s\n", dir, fxp_error());
238 } else {
239 nnames = namesize = 0;
240 ournames = NULL;
241
242 while (1) {
243
244 names = fxp_readdir(dirh);
245 if (names == NULL) {
246 if (fxp_error_type() == SSH_FX_EOF)
247 break;
248 printf("Reading directory %s: %s\n", dir, fxp_error());
249 break;
250 }
251 if (names->nnames == 0) {
252 fxp_free_names(names);
253 break;
254 }
255
256 if (nnames + names->nnames >= namesize) {
257 namesize += names->nnames + 128;
32874aea 258 ournames =
259 srealloc(ournames, namesize * sizeof(*ournames));
4c7f0d61 260 }
261
262 for (i = 0; i < names->nnames; i++)
263 ournames[nnames++] = names->names[i];
264
265 names->nnames = 0; /* prevent free_names */
266 fxp_free_names(names);
267 }
268 fxp_close(dirh);
269
270 /*
271 * Now we have our filenames. Sort them by actual file
272 * name, and then output the longname parts.
273 */
274 qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
275
276 /*
277 * And print them.
278 */
279 for (i = 0; i < nnames; i++)
280 printf("%s\n", ournames[i].longname);
281 }
282
283 sfree(cdir);
284
285 return 0;
286}
287
288/*
289 * Change directories. We do this by canonifying the new name, then
290 * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
291 */
32874aea 292int sftp_cmd_cd(struct sftp_command *cmd)
293{
4c7f0d61 294 struct fxp_handle *dirh;
295 char *dir;
296
297 if (cmd->nwords < 2)
f9e162aa 298 dir = dupstr(homedir);
4c7f0d61 299 else
300 dir = canonify(cmd->words[1]);
301
302 if (!dir) {
303 printf("%s: %s\n", dir, fxp_error());
304 return 0;
305 }
306
307 dirh = fxp_opendir(dir);
308 if (!dirh) {
309 printf("Directory %s: %s\n", dir, fxp_error());
310 sfree(dir);
311 return 0;
312 }
313
314 fxp_close(dirh);
315
316 sfree(pwd);
317 pwd = dir;
318 printf("Remote directory is now %s\n", pwd);
319
320 return 0;
321}
322
323/*
324 * Get a file and save it at the local end.
325 */
32874aea 326int sftp_cmd_get(struct sftp_command *cmd)
327{
4c7f0d61 328 struct fxp_handle *fh;
329 char *fname, *outfname;
330 uint64 offset;
331 FILE *fp;
332
333 if (cmd->nwords < 2) {
334 printf("get: expects a filename\n");
335 return 0;
336 }
337
338 fname = canonify(cmd->words[1]);
339 if (!fname) {
340 printf("%s: %s\n", cmd->words[1], fxp_error());
341 return 0;
342 }
343 outfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
344
345 fh = fxp_open(fname, SSH_FXF_READ);
346 if (!fh) {
347 printf("%s: %s\n", fname, fxp_error());
348 sfree(fname);
349 return 0;
350 }
351 fp = fopen(outfname, "wb");
352 if (!fp) {
353 printf("local: unable to open %s\n", outfname);
32874aea 354 fxp_close(fh);
4c7f0d61 355 sfree(fname);
356 return 0;
357 }
358
359 printf("remote:%s => local:%s\n", fname, outfname);
360
32874aea 361 offset = uint64_make(0, 0);
4c7f0d61 362
363 /*
364 * FIXME: we can use FXP_FSTAT here to get the file size, and
365 * thus put up a progress bar.
366 */
367 while (1) {
368 char buffer[4096];
369 int len;
370 int wpos, wlen;
371
372 len = fxp_read(fh, buffer, offset, sizeof(buffer));
32874aea 373 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
4c7f0d61 374 break;
375 if (len == -1) {
376 printf("error while reading: %s\n", fxp_error());
377 break;
378 }
32874aea 379
4c7f0d61 380 wpos = 0;
381 while (wpos < len) {
32874aea 382 wlen = fwrite(buffer, 1, len - wpos, fp);
4c7f0d61 383 if (wlen <= 0) {
384 printf("error while writing local file\n");
385 break;
386 }
387 wpos += wlen;
388 }
389 if (wpos < len) /* we had an error */
390 break;
391 offset = uint64_add32(offset, len);
392 }
393
394 fclose(fp);
395 fxp_close(fh);
396 sfree(fname);
397
398 return 0;
399}
400
401/*
402 * Send a file and store it at the remote end.
403 */
32874aea 404int sftp_cmd_put(struct sftp_command *cmd)
405{
4c7f0d61 406 struct fxp_handle *fh;
407 char *fname, *origoutfname, *outfname;
408 uint64 offset;
409 FILE *fp;
410
411 if (cmd->nwords < 2) {
412 printf("put: expects a filename\n");
413 return 0;
414 }
415
416 fname = cmd->words[1];
417 origoutfname = (cmd->nwords == 2 ? cmd->words[1] : cmd->words[2]);
418 outfname = canonify(origoutfname);
f9e162aa 419 if (!outfname) {
4c7f0d61 420 printf("%s: %s\n", origoutfname, fxp_error());
421 return 0;
422 }
423
424 fp = fopen(fname, "rb");
425 if (!fp) {
426 printf("local: unable to open %s\n", fname);
4c7f0d61 427 sfree(outfname);
428 return 0;
429 }
430 fh = fxp_open(outfname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
431 if (!fh) {
432 printf("%s: %s\n", outfname, fxp_error());
433 sfree(outfname);
434 return 0;
435 }
436
437 printf("local:%s => remote:%s\n", fname, outfname);
438
32874aea 439 offset = uint64_make(0, 0);
4c7f0d61 440
441 /*
442 * FIXME: we can use FXP_FSTAT here to get the file size, and
443 * thus put up a progress bar.
444 */
445 while (1) {
446 char buffer[4096];
447 int len;
448
f9e162aa 449 len = fread(buffer, 1, sizeof(buffer), fp);
4c7f0d61 450 if (len == -1) {
451 printf("error while reading local file\n");
452 break;
453 } else if (len == 0) {
454 break;
455 }
f9e162aa 456 if (!fxp_write(fh, buffer, offset, len)) {
4c7f0d61 457 printf("error while writing: %s\n", fxp_error());
458 break;
459 }
460 offset = uint64_add32(offset, len);
461 }
462
463 fxp_close(fh);
464 fclose(fp);
465 sfree(outfname);
466
467 return 0;
468}
469
9954aaa3 470int sftp_cmd_mkdir(struct sftp_command *cmd)
471{
472 char *dir;
473 int result;
474
475
476 if (cmd->nwords < 2) {
477 printf("mkdir: expects a directory\n");
478 return 0;
479 }
480
481 dir = canonify(cmd->words[1]);
482 if (!dir) {
483 printf("%s: %s\n", dir, fxp_error());
484 return 0;
485 }
486
487 result = fxp_mkdir(dir);
488 if (!result) {
489 printf("mkdir %s: %s\n", dir, fxp_error());
490 sfree(dir);
491 return 0;
492 }
493
494 sfree(dir);
495 return 0;
496
497}
498
499int sftp_cmd_rmdir(struct sftp_command *cmd)
500{
501 char *dir;
502 int result;
503
504
505 if (cmd->nwords < 2) {
506 printf("rmdir: expects a directory\n");
507 return 0;
508 }
509
510 dir = canonify(cmd->words[1]);
511 if (!dir) {
512 printf("%s: %s\n", dir, fxp_error());
513 return 0;
514 }
515
516 result = fxp_rmdir(dir);
517 if (!result) {
518 printf("rmdir %s: %s\n", dir, fxp_error());
519 sfree(dir);
520 return 0;
521 }
522
523 sfree(dir);
524 return 0;
525
526}
527
528int sftp_cmd_rm(struct sftp_command *cmd)
529{
530 char *fname;
531 int result;
532
533
534 if (cmd->nwords < 2) {
535 printf("rm: expects a filename\n");
536 return 0;
537 }
538
539 fname = canonify(cmd->words[1]);
540 if (!fname) {
541 printf("%s: %s\n", fname, fxp_error());
542 return 0;
543 }
544
545 result = fxp_rm(fname);
546 if (!result) {
547 printf("rm %s: %s\n", fname, fxp_error());
548 sfree(fname);
549 return 0;
550 }
551
552 sfree(fname);
553 return 0;
554
555}
556
557
4c7f0d61 558static struct sftp_cmd_lookup {
559 char *name;
32874aea 560 int (*obey) (struct sftp_command *);
4c7f0d61 561} sftp_lookup[] = {
562 /*
563 * List of sftp commands. This is binary-searched so it MUST be
564 * in ASCII order.
565 */
32874aea 566 {
567 "bye", sftp_cmd_quit}, {
568 "cd", sftp_cmd_cd}, {
569 "dir", sftp_cmd_ls}, {
570 "exit", sftp_cmd_quit}, {
571 "get", sftp_cmd_get}, {
572 "ls", sftp_cmd_ls}, {
9954aaa3 573 "mkdir", sftp_cmd_mkdir}, {
32874aea 574 "put", sftp_cmd_put}, {
9954aaa3 575 "quit", sftp_cmd_quit}, {
576 "rm", sftp_cmd_rm}, {
577 "rmdir", sftp_cmd_rmdir},};
4c7f0d61 578
579/* ----------------------------------------------------------------------
580 * Command line reading and parsing.
581 */
9954aaa3 582struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
32874aea 583{
4c7f0d61 584 char *line;
585 int linelen, linesize;
586 struct sftp_command *cmd;
587 char *p, *q, *r;
588 int quoting;
589
9954aaa3 590 if ((mode == 0) || (modeflags & 1)) {
591 printf("psftp> ");
592 }
4c7f0d61 593 fflush(stdout);
594
595 cmd = smalloc(sizeof(struct sftp_command));
596 cmd->words = NULL;
597 cmd->nwords = 0;
598 cmd->wordssize = 0;
599
600 line = NULL;
601 linesize = linelen = 0;
602 while (1) {
603 int len;
604 char *ret;
605
606 linesize += 512;
607 line = srealloc(line, linesize);
9954aaa3 608 ret = fgets(line + linelen, linesize - linelen, fp);
609 if (modeflags & 1) {
610 printf("%s", ret);
611 }
4c7f0d61 612
613 if (!ret || (linelen == 0 && line[0] == '\0')) {
614 cmd->obey = sftp_cmd_quit;
615 printf("quit\n");
616 return cmd; /* eof */
617 }
32874aea 618 len = linelen + strlen(line + linelen);
4c7f0d61 619 linelen += len;
32874aea 620 if (line[linelen - 1] == '\n') {
4c7f0d61 621 linelen--;
622 line[linelen] = '\0';
623 break;
624 }
625 }
626
627 /*
628 * Parse the command line into words. The syntax is:
629 * - double quotes are removed, but cause spaces within to be
630 * treated as non-separating.
631 * - a double-doublequote pair is a literal double quote, inside
632 * _or_ outside quotes. Like this:
633 *
634 * firstword "second word" "this has ""quotes"" in" sodoes""this""
635 *
636 * becomes
637 *
638 * >firstword<
639 * >second word<
640 * >this has "quotes" in<
641 * >sodoes"this"<
642 */
643 p = line;
644 while (*p) {
645 /* skip whitespace */
32874aea 646 while (*p && (*p == ' ' || *p == '\t'))
647 p++;
4c7f0d61 648 /* mark start of word */
649 q = r = p; /* q sits at start, r writes word */
650 quoting = 0;
651 while (*p) {
652 if (!quoting && (*p == ' ' || *p == '\t'))
653 break; /* reached end of word */
654 else if (*p == '"' && p[1] == '"')
32874aea 655 p += 2, *r++ = '"'; /* a literal quote */
4c7f0d61 656 else if (*p == '"')
657 p++, quoting = !quoting;
658 else
659 *r++ = *p++;
660 }
32874aea 661 if (*p)
662 p++; /* skip over the whitespace */
4c7f0d61 663 *r = '\0';
664 if (cmd->nwords >= cmd->wordssize) {
665 cmd->wordssize = cmd->nwords + 16;
32874aea 666 cmd->words =
667 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
4c7f0d61 668 }
669 cmd->words[cmd->nwords++] = q;
670 }
671
672 /*
673 * Now parse the first word and assign a function.
674 */
675
676 if (cmd->nwords == 0)
677 cmd->obey = sftp_cmd_null;
678 else {
679 int i, j, k, cmp;
680
681 cmd->obey = sftp_cmd_unknown;
682
683 i = -1;
684 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
685 while (j - i > 1) {
686 k = (j + i) / 2;
687 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
688 if (cmp < 0)
689 j = k;
690 else if (cmp > 0)
691 i = k;
692 else {
693 cmd->obey = sftp_lookup[k].obey;
694 break;
695 }
696 }
697 }
698
699 return cmd;
700}
701
9954aaa3 702void do_sftp(int mode, int modeflags, char *batchfile)
32874aea 703{
9954aaa3 704 FILE *fp;
705
4c7f0d61 706 /*
707 * Do protocol initialisation.
708 */
709 if (!fxp_init()) {
710 fprintf(stderr,
32874aea 711 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
0d694692 712 return;
4c7f0d61 713 }
714
715 /*
716 * Find out where our home directory is.
717 */
f9e162aa 718 homedir = fxp_realpath(".");
4c7f0d61 719 if (!homedir) {
720 fprintf(stderr,
721 "Warning: failed to resolve home directory: %s\n",
722 fxp_error());
723 homedir = dupstr(".");
724 } else {
725 printf("Remote working directory is %s\n", homedir);
726 }
727 pwd = dupstr(homedir);
728
9954aaa3 729 /*
730 * Batch mode?
4c7f0d61 731 */
9954aaa3 732 if (mode == 0) {
733
734 /* ------------------------------------------------------------------
735 * Now we're ready to do Real Stuff.
736 */
737 while (1) {
738 struct sftp_command *cmd;
739 cmd = sftp_getcmd(stdin, 0, 0);
740 if (!cmd)
741 break;
742 if (cmd->obey(cmd) < 0)
743 break;
744 }
745 } else {
746 fp = fopen(batchfile, "r");
747 if (!fp) {
748 printf("Fatal: unable to open %s\n", batchfile);
749 return;
750 }
751 while (1) {
752 struct sftp_command *cmd;
753 cmd = sftp_getcmd(fp, mode, modeflags);
754 if (!cmd)
755 break;
756 if (cmd->obey(cmd) < 0)
757 break;
758 if (fxp_error() != NULL) {
759 if (!(modeflags & 2))
760 break;
761 }
762 }
763 fclose(fp);
764
4c7f0d61 765 }
4a8fc3c4 766}
4c7f0d61 767
4a8fc3c4 768/* ----------------------------------------------------------------------
769 * Dirty bits: integration with PuTTY.
770 */
771
772static int verbose = 0;
773
774void verify_ssh_host_key(char *host, int port, char *keytype,
32874aea 775 char *keystr, char *fingerprint)
776{
4a8fc3c4 777 int ret;
d0718310 778 HANDLE hin;
779 DWORD savemode, i;
4a8fc3c4 780
781 static const char absentmsg[] =
32874aea 782 "The server's host key is not cached in the registry. You\n"
783 "have no guarantee that the server is the computer you\n"
784 "think it is.\n"
785 "The server's key fingerprint is:\n"
786 "%s\n"
787 "If you trust this host, enter \"y\" to add the key to\n"
788 "PuTTY's cache and carry on connecting.\n"
d0718310 789 "If you want to carry on connecting just once, without\n"
790 "adding the key to the cache, enter \"n\".\n"
791 "If you do not trust this host, press Return to abandon the\n"
792 "connection.\n"
793 "Store key in cache? (y/n) ";
4a8fc3c4 794
795 static const char wrongmsg[] =
32874aea 796 "WARNING - POTENTIAL SECURITY BREACH!\n"
797 "The server's host key does not match the one PuTTY has\n"
798 "cached in the registry. This means that either the\n"
799 "server administrator has changed the host key, or you\n"
800 "have actually connected to another computer pretending\n"
801 "to be the server.\n"
802 "The new key fingerprint is:\n"
803 "%s\n"
804 "If you were expecting this change and trust the new key,\n"
d0718310 805 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
32874aea 806 "If you want to carry on connecting but without updating\n"
d0718310 807 "the cache, enter \"n\".\n"
32874aea 808 "If you want to abandon the connection completely, press\n"
809 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
810 "safe choice.\n"
811 "Update cached key? (y/n, Return cancels connection) ";
4a8fc3c4 812
813 static const char abandoned[] = "Connection abandoned.\n";
814
815 char line[32];
816
817 /*
818 * Verify the key against the registry.
819 */
820 ret = verify_host_key(host, port, keytype, keystr);
821
32874aea 822 if (ret == 0) /* success - key matched OK */
823 return;
d0718310 824
32874aea 825 if (ret == 2) { /* key was different */
826 fprintf(stderr, wrongmsg, fingerprint);
d0718310 827 fflush(stderr);
4a8fc3c4 828 }
32874aea 829 if (ret == 1) { /* key was absent */
830 fprintf(stderr, absentmsg, fingerprint);
d0718310 831 fflush(stderr);
832 }
833
834 hin = GetStdHandle(STD_INPUT_HANDLE);
835 GetConsoleMode(hin, &savemode);
836 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
837 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
838 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
839 SetConsoleMode(hin, savemode);
840
841 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
842 if (line[0] == 'y' || line[0] == 'Y')
32874aea 843 store_host_key(host, port, keytype, keystr);
d0718310 844 } else {
845 fprintf(stderr, abandoned);
846 exit(0);
4a8fc3c4 847 }
848}
849
850/*
ca20bfcf 851 * Ask whether the selected cipher is acceptable (since it was
852 * below the configured 'warn' threshold).
853 * cs: 0 = both ways, 1 = client->server, 2 = server->client
854 */
855void askcipher(char *ciphername, int cs)
856{
857 HANDLE hin;
858 DWORD savemode, i;
859
860 static const char msg[] =
861 "The first %scipher supported by the server is\n"
862 "%s, which is below the configured warning threshold.\n"
863 "Continue with connection? (y/n) ";
864 static const char abandoned[] = "Connection abandoned.\n";
865
866 char line[32];
867
868 fprintf(stderr, msg,
869 (cs == 0) ? "" :
870 (cs == 1) ? "client-to-server " :
871 "server-to-client ",
872 ciphername);
873 fflush(stderr);
874
875 hin = GetStdHandle(STD_INPUT_HANDLE);
876 GetConsoleMode(hin, &savemode);
877 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
878 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
879 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
880 SetConsoleMode(hin, savemode);
881
882 if (line[0] == 'y' || line[0] == 'Y') {
883 return;
884 } else {
885 fprintf(stderr, abandoned);
886 exit(0);
887 }
888}
889
890/*
4a8fc3c4 891 * Print an error message and perform a fatal exit.
892 */
893void fatalbox(char *fmt, ...)
894{
32874aea 895 char str[0x100]; /* Make the size big enough */
4a8fc3c4 896 va_list ap;
897 va_start(ap, fmt);
898 strcpy(str, "Fatal:");
32874aea 899 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 900 va_end(ap);
901 strcat(str, "\n");
902 fprintf(stderr, str);
903
904 exit(1);
905}
906void connection_fatal(char *fmt, ...)
907{
32874aea 908 char str[0x100]; /* Make the size big enough */
4a8fc3c4 909 va_list ap;
910 va_start(ap, fmt);
911 strcpy(str, "Fatal:");
32874aea 912 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 913 va_end(ap);
914 strcat(str, "\n");
915 fprintf(stderr, str);
916
917 exit(1);
918}
919
32874aea 920void logevent(char *string)
921{
922}
4a8fc3c4 923
32874aea 924void ldisc_send(char *buf, int len)
925{
4a8fc3c4 926 /*
927 * This is only here because of the calls to ldisc_send(NULL,
928 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
929 * ldisc as an ldisc. So if we get called with any real data, I
930 * want to know about it.
4c7f0d61 931 */
4a8fc3c4 932 assert(len == 0);
933}
934
935/*
936 * Be told what socket we're supposed to be using.
937 */
938static SOCKET sftp_ssh_socket;
32874aea 939char *do_select(SOCKET skt, int startup)
940{
4a8fc3c4 941 if (startup)
942 sftp_ssh_socket = skt;
943 else
944 sftp_ssh_socket = INVALID_SOCKET;
945 return NULL;
4c7f0d61 946}
4a8fc3c4 947extern int select_result(WPARAM, LPARAM);
948
949/*
950 * Receive a block of data from the SSH link. Block until all data
951 * is available.
952 *
953 * To do this, we repeatedly call the SSH protocol module, with our
954 * own trap in from_backend() to catch the data that comes back. We
955 * do this until we have enough data.
956 */
957
32874aea 958static unsigned char *outptr; /* where to put the data */
959static unsigned outlen; /* how much data required */
4a8fc3c4 960static unsigned char *pending = NULL; /* any spare data */
32874aea 961static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
5471d09a 962int from_backend(int is_stderr, char *data, int datalen)
32874aea 963{
964 unsigned char *p = (unsigned char *) data;
965 unsigned len = (unsigned) datalen;
4a8fc3c4 966
967 /*
968 * stderr data is just spouted to local stderr and otherwise
969 * ignored.
970 */
971 if (is_stderr) {
972 fwrite(data, 1, len, stderr);
5471d09a 973 return 0;
4a8fc3c4 974 }
975
976 /*
977 * If this is before the real session begins, just return.
978 */
979 if (!outptr)
5471d09a 980 return 0;
4a8fc3c4 981
982 if (outlen > 0) {
32874aea 983 unsigned used = outlen;
984 if (used > len)
985 used = len;
986 memcpy(outptr, p, used);
987 outptr += used;
988 outlen -= used;
989 p += used;
990 len -= used;
4a8fc3c4 991 }
992
993 if (len > 0) {
32874aea 994 if (pendsize < pendlen + len) {
995 pendsize = pendlen + len + 4096;
996 pending = (pending ? srealloc(pending, pendsize) :
997 smalloc(pendsize));
998 if (!pending)
999 fatalbox("Out of memory");
1000 }
1001 memcpy(pending + pendlen, p, len);
1002 pendlen += len;
4a8fc3c4 1003 }
5471d09a 1004
1005 return 0;
4a8fc3c4 1006}
32874aea 1007int sftp_recvdata(char *buf, int len)
1008{
1009 outptr = (unsigned char *) buf;
4a8fc3c4 1010 outlen = len;
1011
1012 /*
1013 * See if the pending-input block contains some of what we
1014 * need.
1015 */
1016 if (pendlen > 0) {
32874aea 1017 unsigned pendused = pendlen;
1018 if (pendused > outlen)
1019 pendused = outlen;
4a8fc3c4 1020 memcpy(outptr, pending, pendused);
32874aea 1021 memmove(pending, pending + pendused, pendlen - pendused);
4a8fc3c4 1022 outptr += pendused;
1023 outlen -= pendused;
32874aea 1024 pendlen -= pendused;
1025 if (pendlen == 0) {
1026 pendsize = 0;
1027 sfree(pending);
1028 pending = NULL;
1029 }
1030 if (outlen == 0)
1031 return 1;
4a8fc3c4 1032 }
1033
1034 while (outlen > 0) {
32874aea 1035 fd_set readfds;
4a8fc3c4 1036
32874aea 1037 FD_ZERO(&readfds);
1038 FD_SET(sftp_ssh_socket, &readfds);
1039 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1040 return 0; /* doom */
1041 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 1042 }
1043
1044 return 1;
1045}
32874aea 1046int sftp_senddata(char *buf, int len)
1047{
1048 back->send((unsigned char *) buf, len);
4a8fc3c4 1049 return 1;
1050}
1051
1052/*
1053 * Loop through the ssh connection and authentication process.
1054 */
32874aea 1055static void ssh_sftp_init(void)
1056{
4a8fc3c4 1057 if (sftp_ssh_socket == INVALID_SOCKET)
1058 return;
1059 while (!back->sendok()) {
32874aea 1060 fd_set readfds;
1061 FD_ZERO(&readfds);
1062 FD_SET(sftp_ssh_socket, &readfds);
1063 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1064 return; /* doom */
1065 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 1066 }
1067}
1068
1069static char *password = NULL;
fa17a66e 1070static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
4a8fc3c4 1071{
1072 HANDLE hin, hout;
fa17a66e 1073 DWORD savemode, newmode, i;
4a8fc3c4 1074
1075 if (password) {
32874aea 1076 static int tried_once = 0;
1077
1078 if (tried_once) {
1079 return 0;
1080 } else {
1081 strncpy(str, password, maxlen);
1082 str[maxlen - 1] = '\0';
1083 tried_once = 1;
1084 return 1;
1085 }
4a8fc3c4 1086 }
1087
1088 hin = GetStdHandle(STD_INPUT_HANDLE);
1089 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1090 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1091 fprintf(stderr, "Cannot get standard input/output handles\n");
1092 exit(1);
1093 }
1094
1095 GetConsoleMode(hin, &savemode);
fa17a66e 1096 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1097 if (is_pw)
32874aea 1098 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 1099 else
32874aea 1100 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 1101 SetConsoleMode(hin, newmode);
4a8fc3c4 1102
1103 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 1104 ReadFile(hin, str, maxlen - 1, &i, NULL);
4a8fc3c4 1105
1106 SetConsoleMode(hin, savemode);
1107
32874aea 1108 if ((int) i > maxlen)
1109 i = maxlen - 1;
1110 else
1111 i = i - 2;
4a8fc3c4 1112 str[i] = '\0';
1113
fa17a66e 1114 if (is_pw)
32874aea 1115 WriteFile(hout, "\r\n", 2, &i, NULL);
4a8fc3c4 1116
1117 return 1;
1118}
1119
1120/*
1121 * Initialize the Win$ock driver.
1122 */
1123static void init_winsock(void)
1124{
1125 WORD winsock_ver;
1126 WSADATA wsadata;
1127
1128 winsock_ver = MAKEWORD(1, 1);
1129 if (WSAStartup(winsock_ver, &wsadata)) {
1130 fprintf(stderr, "Unable to initialise WinSock");
1131 exit(1);
1132 }
32874aea 1133 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
4a8fc3c4 1134 fprintf(stderr, "WinSock version is incompatible with 1.1");
1135 exit(1);
1136 }
1137}
1138
1139/*
1140 * Short description of parameters.
1141 */
1142static void usage(void)
1143{
1144 printf("PuTTY Secure File Transfer (SFTP) client\n");
1145 printf("%s\n", ver);
1146 printf("Usage: psftp [options] user@host\n");
1147 printf("Options:\n");
9954aaa3 1148 printf(" -b file use specified batchfile\n");
1149 printf(" -bc output batchfile commands\n");
1150 printf(" -be don't stop batchfile processing if errors\n");
4a8fc3c4 1151 printf(" -v show verbose messages\n");
1152 printf(" -P port connect to specified port\n");
1153 printf(" -pw passw login with specified password\n");
1154 exit(1);
1155}
1156
1157/*
1158 * Main program. Parse arguments etc.
1159 */
1160int main(int argc, char *argv[])
1161{
1162 int i;
1163 int portnumber = 0;
1164 char *user, *host, *userhost, *realhost;
1165 char *err;
9954aaa3 1166 int mode = 0;
1167 int modeflags = 0;
1168 char *batchfile = NULL;
4a8fc3c4 1169
1170 flags = FLAG_STDERR;
fa17a66e 1171 ssh_get_line = &get_line;
4a8fc3c4 1172 init_winsock();
1173 sk_init();
1174
1175 userhost = user = NULL;
1176
1177 for (i = 1; i < argc; i++) {
1178 if (argv[i][0] != '-') {
1179 if (userhost)
1180 usage();
1181 else
1182 userhost = dupstr(argv[i]);
1183 } else if (strcmp(argv[i], "-v") == 0) {
1184 verbose = 1, flags |= FLAG_VERBOSE;
1185 } else if (strcmp(argv[i], "-h") == 0 ||
1186 strcmp(argv[i], "-?") == 0) {
1187 usage();
32874aea 1188 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
4a8fc3c4 1189 user = argv[++i];
32874aea 1190 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
4a8fc3c4 1191 portnumber = atoi(argv[++i]);
32874aea 1192 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
4a8fc3c4 1193 password = argv[++i];
9954aaa3 1194 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1195 mode = 1;
1196 batchfile = argv[++i];
1197 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1198 modeflags = modeflags | 1;
1199 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1200 modeflags = modeflags | 2;
4a8fc3c4 1201 } else if (strcmp(argv[i], "--") == 0) {
1202 i++;
1203 break;
1204 } else {
1205 usage();
1206 }
1207 }
1208 argc -= i;
1209 argv += i;
1210 back = NULL;
1211
1212 if (argc > 0 || !userhost)
1213 usage();
1214
1215 /* Separate host and username */
1216 host = userhost;
1217 host = strrchr(host, '@');
1218 if (host == NULL) {
1219 host = userhost;
1220 } else {
1221 *host++ = '\0';
1222 if (user) {
32874aea 1223 printf("psftp: multiple usernames specified; using \"%s\"\n",
1224 user);
4a8fc3c4 1225 } else
1226 user = userhost;
1227 }
1228
1229 /* Try to load settings for this host */
1230 do_defaults(host, &cfg);
1231 if (cfg.host[0] == '\0') {
1232 /* No settings for this host; use defaults */
32874aea 1233 do_defaults(NULL, &cfg);
1234 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1235 cfg.host[sizeof(cfg.host) - 1] = '\0';
4a8fc3c4 1236 cfg.port = 22;
1237 }
1238
1239 /* Set username */
1240 if (user != NULL && user[0] != '\0') {
32874aea 1241 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1242 cfg.username[sizeof(cfg.username) - 1] = '\0';
4a8fc3c4 1243 }
1244 if (!cfg.username[0]) {
1245 printf("login as: ");
1246 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1247 fprintf(stderr, "psftp: aborting\n");
1248 exit(1);
1249 } else {
1250 int len = strlen(cfg.username);
32874aea 1251 if (cfg.username[len - 1] == '\n')
1252 cfg.username[len - 1] = '\0';
4a8fc3c4 1253 }
1254 }
1255
1256 if (cfg.protocol != PROT_SSH)
1257 cfg.port = 22;
1258
1259 if (portnumber)
1260 cfg.port = portnumber;
1261
1262 /* SFTP uses SSH2 by default always */
1263 cfg.sshprot = 2;
1264
1265 /* Set up subsystem name. FIXME: fudge for SSH1. */
1266 strcpy(cfg.remote_cmd, "sftp");
1267 cfg.ssh_subsys = TRUE;
1268 cfg.nopty = TRUE;
1269
1270 back = &ssh_backend;
1271
1272 err = back->init(cfg.host, cfg.port, &realhost);
1273 if (err != NULL) {
1274 fprintf(stderr, "ssh_init: %s", err);
1275 return 1;
1276 }
1277 ssh_sftp_init();
1278 if (verbose && realhost != NULL)
1279 printf("Connected to %s\n", realhost);
4c7f0d61 1280
9954aaa3 1281 do_sftp(mode, modeflags, batchfile);
4a8fc3c4 1282
1283 if (back != NULL && back->socket() != NULL) {
1284 char ch;
1285 back->special(TS_EOF);
1286 sftp_recvdata(&ch, 1);
1287 }
1288 WSACleanup();
1289 random_save_seed();
1290
4c7f0d61 1291 return 0;
1292}