Mention PLINK_PROTOCOL in the Plink chapter.
[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
463static struct sftp_cmd_lookup {
464 char *name;
32874aea 465 int (*obey) (struct sftp_command *);
4c7f0d61 466} sftp_lookup[] = {
467 /*
468 * List of sftp commands. This is binary-searched so it MUST be
469 * in ASCII order.
470 */
32874aea 471 {
472 "bye", sftp_cmd_quit}, {
473 "cd", sftp_cmd_cd}, {
474 "dir", sftp_cmd_ls}, {
475 "exit", sftp_cmd_quit}, {
476 "get", sftp_cmd_get}, {
477 "ls", sftp_cmd_ls}, {
478 "put", sftp_cmd_put}, {
479"quit", sftp_cmd_quit},};
4c7f0d61 480
481/* ----------------------------------------------------------------------
482 * Command line reading and parsing.
483 */
32874aea 484struct sftp_command *sftp_getcmd(void)
485{
4c7f0d61 486 char *line;
487 int linelen, linesize;
488 struct sftp_command *cmd;
489 char *p, *q, *r;
490 int quoting;
491
492 printf("psftp> ");
493 fflush(stdout);
494
495 cmd = smalloc(sizeof(struct sftp_command));
496 cmd->words = NULL;
497 cmd->nwords = 0;
498 cmd->wordssize = 0;
499
500 line = NULL;
501 linesize = linelen = 0;
502 while (1) {
503 int len;
504 char *ret;
505
506 linesize += 512;
507 line = srealloc(line, linesize);
32874aea 508 ret = fgets(line + linelen, linesize - linelen, stdin);
4c7f0d61 509
510 if (!ret || (linelen == 0 && line[0] == '\0')) {
511 cmd->obey = sftp_cmd_quit;
512 printf("quit\n");
513 return cmd; /* eof */
514 }
32874aea 515 len = linelen + strlen(line + linelen);
4c7f0d61 516 linelen += len;
32874aea 517 if (line[linelen - 1] == '\n') {
4c7f0d61 518 linelen--;
519 line[linelen] = '\0';
520 break;
521 }
522 }
523
524 /*
525 * Parse the command line into words. The syntax is:
526 * - double quotes are removed, but cause spaces within to be
527 * treated as non-separating.
528 * - a double-doublequote pair is a literal double quote, inside
529 * _or_ outside quotes. Like this:
530 *
531 * firstword "second word" "this has ""quotes"" in" sodoes""this""
532 *
533 * becomes
534 *
535 * >firstword<
536 * >second word<
537 * >this has "quotes" in<
538 * >sodoes"this"<
539 */
540 p = line;
541 while (*p) {
542 /* skip whitespace */
32874aea 543 while (*p && (*p == ' ' || *p == '\t'))
544 p++;
4c7f0d61 545 /* mark start of word */
546 q = r = p; /* q sits at start, r writes word */
547 quoting = 0;
548 while (*p) {
549 if (!quoting && (*p == ' ' || *p == '\t'))
550 break; /* reached end of word */
551 else if (*p == '"' && p[1] == '"')
32874aea 552 p += 2, *r++ = '"'; /* a literal quote */
4c7f0d61 553 else if (*p == '"')
554 p++, quoting = !quoting;
555 else
556 *r++ = *p++;
557 }
32874aea 558 if (*p)
559 p++; /* skip over the whitespace */
4c7f0d61 560 *r = '\0';
561 if (cmd->nwords >= cmd->wordssize) {
562 cmd->wordssize = cmd->nwords + 16;
32874aea 563 cmd->words =
564 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
4c7f0d61 565 }
566 cmd->words[cmd->nwords++] = q;
567 }
568
569 /*
570 * Now parse the first word and assign a function.
571 */
572
573 if (cmd->nwords == 0)
574 cmd->obey = sftp_cmd_null;
575 else {
576 int i, j, k, cmp;
577
578 cmd->obey = sftp_cmd_unknown;
579
580 i = -1;
581 j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
582 while (j - i > 1) {
583 k = (j + i) / 2;
584 cmp = strcmp(cmd->words[0], sftp_lookup[k].name);
585 if (cmp < 0)
586 j = k;
587 else if (cmp > 0)
588 i = k;
589 else {
590 cmd->obey = sftp_lookup[k].obey;
591 break;
592 }
593 }
594 }
595
596 return cmd;
597}
598
32874aea 599void do_sftp(void)
600{
4c7f0d61 601 /*
602 * Do protocol initialisation.
603 */
604 if (!fxp_init()) {
605 fprintf(stderr,
32874aea 606 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
0d694692 607 return;
4c7f0d61 608 }
609
610 /*
611 * Find out where our home directory is.
612 */
f9e162aa 613 homedir = fxp_realpath(".");
4c7f0d61 614 if (!homedir) {
615 fprintf(stderr,
616 "Warning: failed to resolve home directory: %s\n",
617 fxp_error());
618 homedir = dupstr(".");
619 } else {
620 printf("Remote working directory is %s\n", homedir);
621 }
622 pwd = dupstr(homedir);
623
624 /* ------------------------------------------------------------------
625 * Now we're ready to do Real Stuff.
626 */
627 while (1) {
628 struct sftp_command *cmd;
629 cmd = sftp_getcmd();
630 if (!cmd)
631 break;
632 if (cmd->obey(cmd) < 0)
633 break;
634 }
4a8fc3c4 635}
4c7f0d61 636
4a8fc3c4 637/* ----------------------------------------------------------------------
638 * Dirty bits: integration with PuTTY.
639 */
640
641static int verbose = 0;
642
643void verify_ssh_host_key(char *host, int port, char *keytype,
32874aea 644 char *keystr, char *fingerprint)
645{
4a8fc3c4 646 int ret;
d0718310 647 HANDLE hin;
648 DWORD savemode, i;
4a8fc3c4 649
650 static const char absentmsg[] =
32874aea 651 "The server's host key is not cached in the registry. You\n"
652 "have no guarantee that the server is the computer you\n"
653 "think it is.\n"
654 "The server's key fingerprint is:\n"
655 "%s\n"
656 "If you trust this host, enter \"y\" to add the key to\n"
657 "PuTTY's cache and carry on connecting.\n"
d0718310 658 "If you want to carry on connecting just once, without\n"
659 "adding the key to the cache, enter \"n\".\n"
660 "If you do not trust this host, press Return to abandon the\n"
661 "connection.\n"
662 "Store key in cache? (y/n) ";
4a8fc3c4 663
664 static const char wrongmsg[] =
32874aea 665 "WARNING - POTENTIAL SECURITY BREACH!\n"
666 "The server's host key does not match the one PuTTY has\n"
667 "cached in the registry. This means that either the\n"
668 "server administrator has changed the host key, or you\n"
669 "have actually connected to another computer pretending\n"
670 "to be the server.\n"
671 "The new key fingerprint is:\n"
672 "%s\n"
673 "If you were expecting this change and trust the new key,\n"
d0718310 674 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
32874aea 675 "If you want to carry on connecting but without updating\n"
d0718310 676 "the cache, enter \"n\".\n"
32874aea 677 "If you want to abandon the connection completely, press\n"
678 "Return to cancel. Pressing Return is the ONLY guaranteed\n"
679 "safe choice.\n"
680 "Update cached key? (y/n, Return cancels connection) ";
4a8fc3c4 681
682 static const char abandoned[] = "Connection abandoned.\n";
683
684 char line[32];
685
686 /*
687 * Verify the key against the registry.
688 */
689 ret = verify_host_key(host, port, keytype, keystr);
690
32874aea 691 if (ret == 0) /* success - key matched OK */
692 return;
d0718310 693
32874aea 694 if (ret == 2) { /* key was different */
695 fprintf(stderr, wrongmsg, fingerprint);
d0718310 696 fflush(stderr);
4a8fc3c4 697 }
32874aea 698 if (ret == 1) { /* key was absent */
699 fprintf(stderr, absentmsg, fingerprint);
d0718310 700 fflush(stderr);
701 }
702
703 hin = GetStdHandle(STD_INPUT_HANDLE);
704 GetConsoleMode(hin, &savemode);
705 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
706 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
707 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
708 SetConsoleMode(hin, savemode);
709
710 if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
711 if (line[0] == 'y' || line[0] == 'Y')
32874aea 712 store_host_key(host, port, keytype, keystr);
d0718310 713 } else {
714 fprintf(stderr, abandoned);
715 exit(0);
4a8fc3c4 716 }
717}
718
719/*
720 * Print an error message and perform a fatal exit.
721 */
722void fatalbox(char *fmt, ...)
723{
32874aea 724 char str[0x100]; /* Make the size big enough */
4a8fc3c4 725 va_list ap;
726 va_start(ap, fmt);
727 strcpy(str, "Fatal:");
32874aea 728 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 729 va_end(ap);
730 strcat(str, "\n");
731 fprintf(stderr, str);
732
733 exit(1);
734}
735void connection_fatal(char *fmt, ...)
736{
32874aea 737 char str[0x100]; /* Make the size big enough */
4a8fc3c4 738 va_list ap;
739 va_start(ap, fmt);
740 strcpy(str, "Fatal:");
32874aea 741 vsprintf(str + strlen(str), fmt, ap);
4a8fc3c4 742 va_end(ap);
743 strcat(str, "\n");
744 fprintf(stderr, str);
745
746 exit(1);
747}
748
32874aea 749void logevent(char *string)
750{
751}
4a8fc3c4 752
32874aea 753void ldisc_send(char *buf, int len)
754{
4a8fc3c4 755 /*
756 * This is only here because of the calls to ldisc_send(NULL,
757 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
758 * ldisc as an ldisc. So if we get called with any real data, I
759 * want to know about it.
4c7f0d61 760 */
4a8fc3c4 761 assert(len == 0);
762}
763
764/*
765 * Be told what socket we're supposed to be using.
766 */
767static SOCKET sftp_ssh_socket;
32874aea 768char *do_select(SOCKET skt, int startup)
769{
4a8fc3c4 770 if (startup)
771 sftp_ssh_socket = skt;
772 else
773 sftp_ssh_socket = INVALID_SOCKET;
774 return NULL;
4c7f0d61 775}
4a8fc3c4 776extern int select_result(WPARAM, LPARAM);
777
778/*
779 * Receive a block of data from the SSH link. Block until all data
780 * is available.
781 *
782 * To do this, we repeatedly call the SSH protocol module, with our
783 * own trap in from_backend() to catch the data that comes back. We
784 * do this until we have enough data.
785 */
786
32874aea 787static unsigned char *outptr; /* where to put the data */
788static unsigned outlen; /* how much data required */
4a8fc3c4 789static unsigned char *pending = NULL; /* any spare data */
32874aea 790static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
791void from_backend(int is_stderr, char *data, int datalen)
792{
793 unsigned char *p = (unsigned char *) data;
794 unsigned len = (unsigned) datalen;
4a8fc3c4 795
796 /*
797 * stderr data is just spouted to local stderr and otherwise
798 * ignored.
799 */
800 if (is_stderr) {
801 fwrite(data, 1, len, stderr);
802 return;
803 }
804
805 /*
806 * If this is before the real session begins, just return.
807 */
808 if (!outptr)
32874aea 809 return;
4a8fc3c4 810
811 if (outlen > 0) {
32874aea 812 unsigned used = outlen;
813 if (used > len)
814 used = len;
815 memcpy(outptr, p, used);
816 outptr += used;
817 outlen -= used;
818 p += used;
819 len -= used;
4a8fc3c4 820 }
821
822 if (len > 0) {
32874aea 823 if (pendsize < pendlen + len) {
824 pendsize = pendlen + len + 4096;
825 pending = (pending ? srealloc(pending, pendsize) :
826 smalloc(pendsize));
827 if (!pending)
828 fatalbox("Out of memory");
829 }
830 memcpy(pending + pendlen, p, len);
831 pendlen += len;
4a8fc3c4 832 }
833}
32874aea 834int sftp_recvdata(char *buf, int len)
835{
836 outptr = (unsigned char *) buf;
4a8fc3c4 837 outlen = len;
838
839 /*
840 * See if the pending-input block contains some of what we
841 * need.
842 */
843 if (pendlen > 0) {
32874aea 844 unsigned pendused = pendlen;
845 if (pendused > outlen)
846 pendused = outlen;
4a8fc3c4 847 memcpy(outptr, pending, pendused);
32874aea 848 memmove(pending, pending + pendused, pendlen - pendused);
4a8fc3c4 849 outptr += pendused;
850 outlen -= pendused;
32874aea 851 pendlen -= pendused;
852 if (pendlen == 0) {
853 pendsize = 0;
854 sfree(pending);
855 pending = NULL;
856 }
857 if (outlen == 0)
858 return 1;
4a8fc3c4 859 }
860
861 while (outlen > 0) {
32874aea 862 fd_set readfds;
4a8fc3c4 863
32874aea 864 FD_ZERO(&readfds);
865 FD_SET(sftp_ssh_socket, &readfds);
866 if (select(1, &readfds, NULL, NULL, NULL) < 0)
867 return 0; /* doom */
868 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 869 }
870
871 return 1;
872}
32874aea 873int sftp_senddata(char *buf, int len)
874{
875 back->send((unsigned char *) buf, len);
4a8fc3c4 876 return 1;
877}
878
879/*
880 * Loop through the ssh connection and authentication process.
881 */
32874aea 882static void ssh_sftp_init(void)
883{
4a8fc3c4 884 if (sftp_ssh_socket == INVALID_SOCKET)
885 return;
886 while (!back->sendok()) {
32874aea 887 fd_set readfds;
888 FD_ZERO(&readfds);
889 FD_SET(sftp_ssh_socket, &readfds);
890 if (select(1, &readfds, NULL, NULL, NULL) < 0)
891 return; /* doom */
892 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
4a8fc3c4 893 }
894}
895
896static char *password = NULL;
fa17a66e 897static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
4a8fc3c4 898{
899 HANDLE hin, hout;
fa17a66e 900 DWORD savemode, newmode, i;
4a8fc3c4 901
902 if (password) {
32874aea 903 static int tried_once = 0;
904
905 if (tried_once) {
906 return 0;
907 } else {
908 strncpy(str, password, maxlen);
909 str[maxlen - 1] = '\0';
910 tried_once = 1;
911 return 1;
912 }
4a8fc3c4 913 }
914
915 hin = GetStdHandle(STD_INPUT_HANDLE);
916 hout = GetStdHandle(STD_OUTPUT_HANDLE);
917 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
918 fprintf(stderr, "Cannot get standard input/output handles\n");
919 exit(1);
920 }
921
922 GetConsoleMode(hin, &savemode);
fa17a66e 923 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
924 if (is_pw)
32874aea 925 newmode &= ~ENABLE_ECHO_INPUT;
fa17a66e 926 else
32874aea 927 newmode |= ENABLE_ECHO_INPUT;
fa17a66e 928 SetConsoleMode(hin, newmode);
4a8fc3c4 929
930 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
32874aea 931 ReadFile(hin, str, maxlen - 1, &i, NULL);
4a8fc3c4 932
933 SetConsoleMode(hin, savemode);
934
32874aea 935 if ((int) i > maxlen)
936 i = maxlen - 1;
937 else
938 i = i - 2;
4a8fc3c4 939 str[i] = '\0';
940
fa17a66e 941 if (is_pw)
32874aea 942 WriteFile(hout, "\r\n", 2, &i, NULL);
4a8fc3c4 943
944 return 1;
945}
946
947/*
948 * Initialize the Win$ock driver.
949 */
950static void init_winsock(void)
951{
952 WORD winsock_ver;
953 WSADATA wsadata;
954
955 winsock_ver = MAKEWORD(1, 1);
956 if (WSAStartup(winsock_ver, &wsadata)) {
957 fprintf(stderr, "Unable to initialise WinSock");
958 exit(1);
959 }
32874aea 960 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
4a8fc3c4 961 fprintf(stderr, "WinSock version is incompatible with 1.1");
962 exit(1);
963 }
964}
965
966/*
967 * Short description of parameters.
968 */
969static void usage(void)
970{
971 printf("PuTTY Secure File Transfer (SFTP) client\n");
972 printf("%s\n", ver);
973 printf("Usage: psftp [options] user@host\n");
974 printf("Options:\n");
975 printf(" -v show verbose messages\n");
976 printf(" -P port connect to specified port\n");
977 printf(" -pw passw login with specified password\n");
978 exit(1);
979}
980
981/*
982 * Main program. Parse arguments etc.
983 */
984int main(int argc, char *argv[])
985{
986 int i;
987 int portnumber = 0;
988 char *user, *host, *userhost, *realhost;
989 char *err;
990
991 flags = FLAG_STDERR;
fa17a66e 992 ssh_get_line = &get_line;
4a8fc3c4 993 init_winsock();
994 sk_init();
995
996 userhost = user = NULL;
997
998 for (i = 1; i < argc; i++) {
999 if (argv[i][0] != '-') {
1000 if (userhost)
1001 usage();
1002 else
1003 userhost = dupstr(argv[i]);
1004 } else if (strcmp(argv[i], "-v") == 0) {
1005 verbose = 1, flags |= FLAG_VERBOSE;
1006 } else if (strcmp(argv[i], "-h") == 0 ||
1007 strcmp(argv[i], "-?") == 0) {
1008 usage();
32874aea 1009 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
4a8fc3c4 1010 user = argv[++i];
32874aea 1011 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
4a8fc3c4 1012 portnumber = atoi(argv[++i]);
32874aea 1013 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
4a8fc3c4 1014 password = argv[++i];
1015 } else if (strcmp(argv[i], "--") == 0) {
1016 i++;
1017 break;
1018 } else {
1019 usage();
1020 }
1021 }
1022 argc -= i;
1023 argv += i;
1024 back = NULL;
1025
1026 if (argc > 0 || !userhost)
1027 usage();
1028
1029 /* Separate host and username */
1030 host = userhost;
1031 host = strrchr(host, '@');
1032 if (host == NULL) {
1033 host = userhost;
1034 } else {
1035 *host++ = '\0';
1036 if (user) {
32874aea 1037 printf("psftp: multiple usernames specified; using \"%s\"\n",
1038 user);
4a8fc3c4 1039 } else
1040 user = userhost;
1041 }
1042
1043 /* Try to load settings for this host */
1044 do_defaults(host, &cfg);
1045 if (cfg.host[0] == '\0') {
1046 /* No settings for this host; use defaults */
32874aea 1047 do_defaults(NULL, &cfg);
1048 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1049 cfg.host[sizeof(cfg.host) - 1] = '\0';
4a8fc3c4 1050 cfg.port = 22;
1051 }
1052
1053 /* Set username */
1054 if (user != NULL && user[0] != '\0') {
32874aea 1055 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1056 cfg.username[sizeof(cfg.username) - 1] = '\0';
4a8fc3c4 1057 }
1058 if (!cfg.username[0]) {
1059 printf("login as: ");
1060 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1061 fprintf(stderr, "psftp: aborting\n");
1062 exit(1);
1063 } else {
1064 int len = strlen(cfg.username);
32874aea 1065 if (cfg.username[len - 1] == '\n')
1066 cfg.username[len - 1] = '\0';
4a8fc3c4 1067 }
1068 }
1069
1070 if (cfg.protocol != PROT_SSH)
1071 cfg.port = 22;
1072
1073 if (portnumber)
1074 cfg.port = portnumber;
1075
1076 /* SFTP uses SSH2 by default always */
1077 cfg.sshprot = 2;
1078
1079 /* Set up subsystem name. FIXME: fudge for SSH1. */
1080 strcpy(cfg.remote_cmd, "sftp");
1081 cfg.ssh_subsys = TRUE;
1082 cfg.nopty = TRUE;
1083
1084 back = &ssh_backend;
1085
1086 err = back->init(cfg.host, cfg.port, &realhost);
1087 if (err != NULL) {
1088 fprintf(stderr, "ssh_init: %s", err);
1089 return 1;
1090 }
1091 ssh_sftp_init();
1092 if (verbose && realhost != NULL)
1093 printf("Connected to %s\n", realhost);
4c7f0d61 1094
4c7f0d61 1095 do_sftp();
4a8fc3c4 1096
1097 if (back != NULL && back->socket() != NULL) {
1098 char ch;
1099 back->special(TS_EOF);
1100 sftp_recvdata(&ch, 1);
1101 }
1102 WSACleanup();
1103 random_save_seed();
1104
4c7f0d61 1105 return 0;
1106}