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