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