Extensive changes that _should_ fix the socket buffering problems,
[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/*
851 * Print an error message and perform a fatal exit.
852 */
853void fatalbox(char *fmt, ...)
854{
32874aea 855 char str[0x100]; /* Make the size big enough */
4a8fc3c4 856 va_list ap;
857 va_start(ap, fmt);
858 strcpy(str, "Fatal:");
32874aea 859 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 860 va_end(ap);
861 strcat(str, "\n");
862 fprintf(stderr, str);
863
864 exit(1);
865}
866void connection_fatal(char *fmt, ...)
867{
32874aea 868 char str[0x100]; /* Make the size big enough */
4a8fc3c4 869 va_list ap;
870 va_start(ap, fmt);
871 strcpy(str, "Fatal:");
32874aea 872 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 873 va_end(ap);
874 strcat(str, "\n");
875 fprintf(stderr, str);
876
877 exit(1);
878}
879
32874aea 880void logevent(char *string)
881{
882}
4a8fc3c4 883
32874aea 884void ldisc_send(char *buf, int len)
885{
4a8fc3c4 886 /*
887 * This is only here because of the calls to ldisc_send(NULL,
888 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
889 * ldisc as an ldisc. So if we get called with any real data, I
890 * want to know about it.
4c7f0d61 891 */
4a8fc3c4 892 assert(len == 0);
893}
894
895/*
896 * Be told what socket we're supposed to be using.
897 */
898static SOCKET sftp_ssh_socket;
32874aea 899char *do_select(SOCKET skt, int startup)
900{
4a8fc3c4 901 if (startup)
902 sftp_ssh_socket = skt;
903 else
904 sftp_ssh_socket = INVALID_SOCKET;
905 return NULL;
4c7f0d61 906}
4a8fc3c4 907extern int select_result(WPARAM, LPARAM);
908
909/*
910 * Receive a block of data from the SSH link. Block until all data
911 * is available.
912 *
913 * To do this, we repeatedly call the SSH protocol module, with our
914 * own trap in from_backend() to catch the data that comes back. We
915 * do this until we have enough data.
916 */
917
32874aea 918static unsigned char *outptr; /* where to put the data */
919static unsigned outlen; /* how much data required */
4a8fc3c4 920static unsigned char *pending = NULL; /* any spare data */
32874aea 921static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
5471d09a 922int from_backend(int is_stderr, char *data, int datalen)
32874aea 923{
924 unsigned char *p = (unsigned char *) data;
925 unsigned len = (unsigned) datalen;
4a8fc3c4 926
927 /*
928 * stderr data is just spouted to local stderr and otherwise
929 * ignored.
930 */
931 if (is_stderr) {
932 fwrite(data, 1, len, stderr);
5471d09a 933 return 0;
4a8fc3c4 934 }
935
936 /*
937 * If this is before the real session begins, just return.
938 */
939 if (!outptr)
5471d09a 940 return 0;
4a8fc3c4 941
942 if (outlen > 0) {
32874aea 943 unsigned used = outlen;
944 if (used > len)
945 used = len;
946 memcpy(outptr, p, used);
947 outptr += used;
948 outlen -= used;
949 p += used;
950 len -= used;
4a8fc3c4 951 }
952
953 if (len > 0) {
32874aea 954 if (pendsize < pendlen + len) {
955 pendsize = pendlen + len + 4096;
956 pending = (pending ? srealloc(pending, pendsize) :
957 smalloc(pendsize));
958 if (!pending)
959 fatalbox("Out of memory");
960 }
961 memcpy(pending + pendlen, p, len);
962 pendlen += len;
4a8fc3c4 963 }
5471d09a 964
965 return 0;
4a8fc3c4 966}
32874aea 967int sftp_recvdata(char *buf, int len)
968{
969 outptr = (unsigned char *) buf;
4a8fc3c4 970 outlen = len;
971
972 /*
973 * See if the pending-input block contains some of what we
974 * need.
975 */
976 if (pendlen > 0) {
32874aea 977 unsigned pendused = pendlen;
978 if (pendused > outlen)
979 pendused = outlen;
4a8fc3c4 980 memcpy(outptr, pending, pendused);
32874aea 981 memmove(pending, pending + pendused, pendlen - pendused);
4a8fc3c4 982 outptr += pendused;
983 outlen -= pendused;
32874aea 984 pendlen -= pendused;
985 if (pendlen == 0) {
986 pendsize = 0;
987 sfree(pending);
988 pending = NULL;
989 }
990 if (outlen == 0)
991 return 1;
4a8fc3c4 992 }
993
994 while (outlen > 0) {
32874aea 995 fd_set readfds;
4a8fc3c4 996
32874aea 997 FD_ZERO(&readfds);
998 FD_SET(sftp_ssh_socket, &readfds);
999 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1000 return 0; /* doom */
1001 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 1002 }
1003
1004 return 1;
1005}
32874aea 1006int sftp_senddata(char *buf, int len)
1007{
1008 back->send((unsigned char *) buf, len);
4a8fc3c4 1009 return 1;
1010}
1011
1012/*
1013 * Loop through the ssh connection and authentication process.
1014 */
32874aea 1015static void ssh_sftp_init(void)
1016{
4a8fc3c4 1017 if (sftp_ssh_socket == INVALID_SOCKET)
1018 return;
1019 while (!back->sendok()) {
32874aea 1020 fd_set readfds;
1021 FD_ZERO(&readfds);
1022 FD_SET(sftp_ssh_socket, &readfds);
1023 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1024 return; /* doom */
1025 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 1026 }
1027}
1028
1029static char *password = NULL;
fa17a66e 1030static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
4a8fc3c4 1031{
1032 HANDLE hin, hout;
fa17a66e 1033 DWORD savemode, newmode, i;
4a8fc3c4 1034
1035 if (password) {
32874aea 1036 static int tried_once = 0;
1037
1038 if (tried_once) {
1039 return 0;
1040 } else {
1041 strncpy(str, password, maxlen);
1042 str[maxlen - 1] = '\0';
1043 tried_once = 1;
1044 return 1;
1045 }
4a8fc3c4 1046 }
1047
1048 hin = GetStdHandle(STD_INPUT_HANDLE);
1049 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1050 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1051 fprintf(stderr, "Cannot get standard input/output handles\n");
1052 exit(1);
1053 }
1054
1055 GetConsoleMode(hin, &savemode);
fa17a66e 1056 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1057 if (is_pw)
32874aea 1058 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 1059 else
32874aea 1060 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 1061 SetConsoleMode(hin, newmode);
4a8fc3c4 1062
1063 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 1064 ReadFile(hin, str, maxlen - 1, &i, NULL);
4a8fc3c4 1065
1066 SetConsoleMode(hin, savemode);
1067
32874aea 1068 if ((int) i > maxlen)
1069 i = maxlen - 1;
1070 else
1071 i = i - 2;
4a8fc3c4 1072 str[i] = '\0';
1073
fa17a66e 1074 if (is_pw)
32874aea 1075 WriteFile(hout, "\r\n", 2, &i, NULL);
4a8fc3c4 1076
1077 return 1;
1078}
1079
1080/*
1081 * Initialize the Win$ock driver.
1082 */
1083static void init_winsock(void)
1084{
1085 WORD winsock_ver;
1086 WSADATA wsadata;
1087
1088 winsock_ver = MAKEWORD(1, 1);
1089 if (WSAStartup(winsock_ver, &wsadata)) {
1090 fprintf(stderr, "Unable to initialise WinSock");
1091 exit(1);
1092 }
32874aea 1093 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
4a8fc3c4 1094 fprintf(stderr, "WinSock version is incompatible with 1.1");
1095 exit(1);
1096 }
1097}
1098
1099/*
1100 * Short description of parameters.
1101 */
1102static void usage(void)
1103{
1104 printf("PuTTY Secure File Transfer (SFTP) client\n");
1105 printf("%s\n", ver);
1106 printf("Usage: psftp [options] user@host\n");
1107 printf("Options:\n");
9954aaa3 1108 printf(" -b file use specified batchfile\n");
1109 printf(" -bc output batchfile commands\n");
1110 printf(" -be don't stop batchfile processing if errors\n");
4a8fc3c4 1111 printf(" -v show verbose messages\n");
1112 printf(" -P port connect to specified port\n");
1113 printf(" -pw passw login with specified password\n");
1114 exit(1);
1115}
1116
1117/*
1118 * Main program. Parse arguments etc.
1119 */
1120int main(int argc, char *argv[])
1121{
1122 int i;
1123 int portnumber = 0;
1124 char *user, *host, *userhost, *realhost;
1125 char *err;
9954aaa3 1126 int mode = 0;
1127 int modeflags = 0;
1128 char *batchfile = NULL;
4a8fc3c4 1129
1130 flags = FLAG_STDERR;
fa17a66e 1131 ssh_get_line = &get_line;
4a8fc3c4 1132 init_winsock();
1133 sk_init();
1134
1135 userhost = user = NULL;
1136
1137 for (i = 1; i < argc; i++) {
1138 if (argv[i][0] != '-') {
1139 if (userhost)
1140 usage();
1141 else
1142 userhost = dupstr(argv[i]);
1143 } else if (strcmp(argv[i], "-v") == 0) {
1144 verbose = 1, flags |= FLAG_VERBOSE;
1145 } else if (strcmp(argv[i], "-h") == 0 ||
1146 strcmp(argv[i], "-?") == 0) {
1147 usage();
32874aea 1148 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
4a8fc3c4 1149 user = argv[++i];
32874aea 1150 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
4a8fc3c4 1151 portnumber = atoi(argv[++i]);
32874aea 1152 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
4a8fc3c4 1153 password = argv[++i];
9954aaa3 1154 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1155 mode = 1;
1156 batchfile = argv[++i];
1157 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1158 modeflags = modeflags | 1;
1159 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1160 modeflags = modeflags | 2;
4a8fc3c4 1161 } else if (strcmp(argv[i], "--") == 0) {
1162 i++;
1163 break;
1164 } else {
1165 usage();
1166 }
1167 }
1168 argc -= i;
1169 argv += i;
1170 back = NULL;
1171
1172 if (argc > 0 || !userhost)
1173 usage();
1174
1175 /* Separate host and username */
1176 host = userhost;
1177 host = strrchr(host, '@');
1178 if (host == NULL) {
1179 host = userhost;
1180 } else {
1181 *host++ = '\0';
1182 if (user) {
32874aea 1183 printf("psftp: multiple usernames specified; using \"%s\"\n",
1184 user);
4a8fc3c4 1185 } else
1186 user = userhost;
1187 }
1188
1189 /* Try to load settings for this host */
1190 do_defaults(host, &cfg);
1191 if (cfg.host[0] == '\0') {
1192 /* No settings for this host; use defaults */
32874aea 1193 do_defaults(NULL, &cfg);
1194 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1195 cfg.host[sizeof(cfg.host) - 1] = '\0';
4a8fc3c4 1196 cfg.port = 22;
1197 }
1198
1199 /* Set username */
1200 if (user != NULL && user[0] != '\0') {
32874aea 1201 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1202 cfg.username[sizeof(cfg.username) - 1] = '\0';
4a8fc3c4 1203 }
1204 if (!cfg.username[0]) {
1205 printf("login as: ");
1206 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1207 fprintf(stderr, "psftp: aborting\n");
1208 exit(1);
1209 } else {
1210 int len = strlen(cfg.username);
32874aea 1211 if (cfg.username[len - 1] == '\n')
1212 cfg.username[len - 1] = '\0';
4a8fc3c4 1213 }
1214 }
1215
1216 if (cfg.protocol != PROT_SSH)
1217 cfg.port = 22;
1218
1219 if (portnumber)
1220 cfg.port = portnumber;
1221
1222 /* SFTP uses SSH2 by default always */
1223 cfg.sshprot = 2;
1224
1225 /* Set up subsystem name. FIXME: fudge for SSH1. */
1226 strcpy(cfg.remote_cmd, "sftp");
1227 cfg.ssh_subsys = TRUE;
1228 cfg.nopty = TRUE;
1229
1230 back = &ssh_backend;
1231
1232 err = back->init(cfg.host, cfg.port, &realhost);
1233 if (err != NULL) {
1234 fprintf(stderr, "ssh_init: %s", err);
1235 return 1;
1236 }
1237 ssh_sftp_init();
1238 if (verbose && realhost != NULL)
1239 printf("Connected to %s\n", realhost);
4c7f0d61 1240
9954aaa3 1241 do_sftp(mode, modeflags, batchfile);
4a8fc3c4 1242
1243 if (back != NULL && back->socket() != NULL) {
1244 char ch;
1245 back->special(TS_EOF);
1246 sftp_recvdata(&ch, 1);
1247 }
1248 WSACleanup();
1249 random_save_seed();
1250
4c7f0d61 1251 return 0;
1252}