ef99cde1588eb7fd87db420a601ab199ae00ce03
[u/mdw/putty] / psftp.c
1 /*
2 * psftp.c: front end for PSFTP.
3 */
4
5 #include <windows.h>
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <stdarg.h>
10 #include <assert.h>
11
12 #define PUTTY_DO_GLOBALS
13 #include "putty.h"
14 #include "storage.h"
15 #include "ssh.h"
16 #include "sftp.h"
17 #include "int64.h"
18
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
26 /* ----------------------------------------------------------------------
27 * String handling routines.
28 */
29
30 char *dupstr(char *s)
31 {
32 int len = strlen(s);
33 char *p = smalloc(len + 1);
34 strcpy(p, s);
35 return p;
36 }
37
38 /* Allocate the concatenation of N strings. Terminate arg list with NULL. */
39 char *dupcat(char *s1, ...)
40 {
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
55 p = smalloc(len + 1);
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
72 /* ----------------------------------------------------------------------
73 * sftp client state.
74 */
75
76 char *pwd, *homedir;
77
78 /* ----------------------------------------------------------------------
79 * Higher-level helper functions used in commands.
80 */
81
82 /*
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).
86 */
87 char *canonify(char *name)
88 {
89 char *fullname, *canonname;
90
91 if (name[0] == '/') {
92 fullname = dupstr(name);
93 } else {
94 char *slash;
95 if (pwd[strlen(pwd) - 1] == '/')
96 slash = "";
97 else
98 slash = "/";
99 fullname = dupcat(pwd, slash, name, NULL);
100 }
101
102 canonname = fxp_realpath(fullname);
103
104 if (canonname) {
105 sfree(fullname);
106 return canonname;
107 } else {
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;
175 }
176 }
177
178 /* ----------------------------------------------------------------------
179 * Actual sftp commands.
180 */
181 struct sftp_command {
182 char **words;
183 int nwords, wordssize;
184 int (*obey) (struct sftp_command *); /* returns <0 to quit */
185 };
186
187 int sftp_cmd_null(struct sftp_command *cmd)
188 {
189 return 0;
190 }
191
192 int sftp_cmd_unknown(struct sftp_command *cmd)
193 {
194 printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
195 return 0;
196 }
197
198 int sftp_cmd_quit(struct sftp_command *cmd)
199 {
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 */
207 static 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;
211 return strcmp(a->filename, b->filename);
212 }
213 int sftp_cmd_ls(struct sftp_command *cmd)
214 {
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;
258 ournames =
259 srealloc(ournames, namesize * sizeof(*ournames));
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 */
292 int sftp_cmd_cd(struct sftp_command *cmd)
293 {
294 struct fxp_handle *dirh;
295 char *dir;
296
297 if (cmd->nwords < 2)
298 dir = dupstr(homedir);
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 */
326 int sftp_cmd_get(struct sftp_command *cmd)
327 {
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);
354 fxp_close(fh);
355 sfree(fname);
356 return 0;
357 }
358
359 printf("remote:%s => local:%s\n", fname, outfname);
360
361 offset = uint64_make(0, 0);
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));
373 if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
374 break;
375 if (len == -1) {
376 printf("error while reading: %s\n", fxp_error());
377 break;
378 }
379
380 wpos = 0;
381 while (wpos < len) {
382 wlen = fwrite(buffer, 1, len - wpos, fp);
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 */
404 int sftp_cmd_put(struct sftp_command *cmd)
405 {
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);
419 if (!outfname) {
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);
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
439 offset = uint64_make(0, 0);
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
449 len = fread(buffer, 1, sizeof(buffer), fp);
450 if (len == -1) {
451 printf("error while reading local file\n");
452 break;
453 } else if (len == 0) {
454 break;
455 }
456 if (!fxp_write(fh, buffer, offset, len)) {
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
470 int 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
499 int 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
528 int 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
558 static struct sftp_cmd_lookup {
559 char *name;
560 int (*obey) (struct sftp_command *);
561 } sftp_lookup[] = {
562 /*
563 * List of sftp commands. This is binary-searched so it MUST be
564 * in ASCII order.
565 */
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}, {
573 "mkdir", sftp_cmd_mkdir}, {
574 "put", sftp_cmd_put}, {
575 "quit", sftp_cmd_quit}, {
576 "rm", sftp_cmd_rm}, {
577 "rmdir", sftp_cmd_rmdir},};
578
579 /* ----------------------------------------------------------------------
580 * Command line reading and parsing.
581 */
582 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
583 {
584 char *line;
585 int linelen, linesize;
586 struct sftp_command *cmd;
587 char *p, *q, *r;
588 int quoting;
589
590 if ((mode == 0) || (modeflags & 1)) {
591 printf("psftp> ");
592 }
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);
608 ret = fgets(line + linelen, linesize - linelen, fp);
609 if (modeflags & 1) {
610 printf("%s", ret);
611 }
612
613 if (!ret || (linelen == 0 && line[0] == '\0')) {
614 cmd->obey = sftp_cmd_quit;
615 printf("quit\n");
616 return cmd; /* eof */
617 }
618 len = linelen + strlen(line + linelen);
619 linelen += len;
620 if (line[linelen - 1] == '\n') {
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 */
646 while (*p && (*p == ' ' || *p == '\t'))
647 p++;
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] == '"')
655 p += 2, *r++ = '"'; /* a literal quote */
656 else if (*p == '"')
657 p++, quoting = !quoting;
658 else
659 *r++ = *p++;
660 }
661 if (*p)
662 p++; /* skip over the whitespace */
663 *r = '\0';
664 if (cmd->nwords >= cmd->wordssize) {
665 cmd->wordssize = cmd->nwords + 16;
666 cmd->words =
667 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
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
702 void do_sftp(int mode, int modeflags, char *batchfile)
703 {
704 FILE *fp;
705
706 /*
707 * Do protocol initialisation.
708 */
709 if (!fxp_init()) {
710 fprintf(stderr,
711 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
712 return;
713 }
714
715 /*
716 * Find out where our home directory is.
717 */
718 homedir = fxp_realpath(".");
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
729 /*
730 * Batch mode?
731 */
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
765 }
766 }
767
768 /* ----------------------------------------------------------------------
769 * Dirty bits: integration with PuTTY.
770 */
771
772 static int verbose = 0;
773
774 void verify_ssh_host_key(char *host, int port, char *keytype,
775 char *keystr, char *fingerprint)
776 {
777 int ret;
778 HANDLE hin;
779 DWORD savemode, i;
780
781 static const char absentmsg[] =
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"
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) ";
794
795 static const char wrongmsg[] =
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"
805 "enter \"y\" to update PuTTY's cache and continue connecting.\n"
806 "If you want to carry on connecting but without updating\n"
807 "the cache, enter \"n\".\n"
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) ";
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
822 if (ret == 0) /* success - key matched OK */
823 return;
824
825 if (ret == 2) { /* key was different */
826 fprintf(stderr, wrongmsg, fingerprint);
827 fflush(stderr);
828 }
829 if (ret == 1) { /* key was absent */
830 fprintf(stderr, absentmsg, fingerprint);
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')
843 store_host_key(host, port, keytype, keystr);
844 } else {
845 fprintf(stderr, abandoned);
846 exit(0);
847 }
848 }
849
850 /*
851 * Ask whether the selected cipher is acceptable (since it was
852 * below the configured 'warn' threshold).
853 * cs: 0 = both ways, 1 = client->server, 2 = server->client
854 */
855 void askcipher(char *ciphername, int cs)
856 {
857 HANDLE hin;
858 DWORD savemode, i;
859
860 static const char msg[] =
861 "The first %scipher supported by the server is\n"
862 "%s, which is below the configured warning threshold.\n"
863 "Continue with connection? (y/n) ";
864 static const char abandoned[] = "Connection abandoned.\n";
865
866 char line[32];
867
868 fprintf(stderr, msg,
869 (cs == 0) ? "" :
870 (cs == 1) ? "client-to-server " :
871 "server-to-client ",
872 ciphername);
873 fflush(stderr);
874
875 hin = GetStdHandle(STD_INPUT_HANDLE);
876 GetConsoleMode(hin, &savemode);
877 SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
878 ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
879 ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
880 SetConsoleMode(hin, savemode);
881
882 if (line[0] == 'y' || line[0] == 'Y') {
883 return;
884 } else {
885 fprintf(stderr, abandoned);
886 exit(0);
887 }
888 }
889
890 /*
891 * Print an error message and perform a fatal exit.
892 */
893 void fatalbox(char *fmt, ...)
894 {
895 char str[0x100]; /* Make the size big enough */
896 va_list ap;
897 va_start(ap, fmt);
898 strcpy(str, "Fatal:");
899 vsprintf(str + strlen(str), fmt, ap);
900 va_end(ap);
901 strcat(str, "\n");
902 fprintf(stderr, str);
903
904 exit(1);
905 }
906 void connection_fatal(char *fmt, ...)
907 {
908 char str[0x100]; /* Make the size big enough */
909 va_list ap;
910 va_start(ap, fmt);
911 strcpy(str, "Fatal:");
912 vsprintf(str + strlen(str), fmt, ap);
913 va_end(ap);
914 strcat(str, "\n");
915 fprintf(stderr, str);
916
917 exit(1);
918 }
919
920 void logevent(char *string)
921 {
922 }
923
924 void ldisc_send(char *buf, int len)
925 {
926 /*
927 * This is only here because of the calls to ldisc_send(NULL,
928 * 0) in ssh.c. Nothing in PSFTP actually needs to use the
929 * ldisc as an ldisc. So if we get called with any real data, I
930 * want to know about it.
931 */
932 assert(len == 0);
933 }
934
935 /*
936 * Be told what socket we're supposed to be using.
937 */
938 static SOCKET sftp_ssh_socket;
939 char *do_select(SOCKET skt, int startup)
940 {
941 if (startup)
942 sftp_ssh_socket = skt;
943 else
944 sftp_ssh_socket = INVALID_SOCKET;
945 return NULL;
946 }
947 extern int select_result(WPARAM, LPARAM);
948
949 /*
950 * Receive a block of data from the SSH link. Block until all data
951 * is available.
952 *
953 * To do this, we repeatedly call the SSH protocol module, with our
954 * own trap in from_backend() to catch the data that comes back. We
955 * do this until we have enough data.
956 */
957
958 static unsigned char *outptr; /* where to put the data */
959 static unsigned outlen; /* how much data required */
960 static unsigned char *pending = NULL; /* any spare data */
961 static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
962 int from_backend(int is_stderr, char *data, int datalen)
963 {
964 unsigned char *p = (unsigned char *) data;
965 unsigned len = (unsigned) datalen;
966
967 /*
968 * stderr data is just spouted to local stderr and otherwise
969 * ignored.
970 */
971 if (is_stderr) {
972 fwrite(data, 1, len, stderr);
973 return 0;
974 }
975
976 /*
977 * If this is before the real session begins, just return.
978 */
979 if (!outptr)
980 return 0;
981
982 if (outlen > 0) {
983 unsigned used = outlen;
984 if (used > len)
985 used = len;
986 memcpy(outptr, p, used);
987 outptr += used;
988 outlen -= used;
989 p += used;
990 len -= used;
991 }
992
993 if (len > 0) {
994 if (pendsize < pendlen + len) {
995 pendsize = pendlen + len + 4096;
996 pending = (pending ? srealloc(pending, pendsize) :
997 smalloc(pendsize));
998 if (!pending)
999 fatalbox("Out of memory");
1000 }
1001 memcpy(pending + pendlen, p, len);
1002 pendlen += len;
1003 }
1004
1005 return 0;
1006 }
1007 int sftp_recvdata(char *buf, int len)
1008 {
1009 outptr = (unsigned char *) buf;
1010 outlen = len;
1011
1012 /*
1013 * See if the pending-input block contains some of what we
1014 * need.
1015 */
1016 if (pendlen > 0) {
1017 unsigned pendused = pendlen;
1018 if (pendused > outlen)
1019 pendused = outlen;
1020 memcpy(outptr, pending, pendused);
1021 memmove(pending, pending + pendused, pendlen - pendused);
1022 outptr += pendused;
1023 outlen -= pendused;
1024 pendlen -= pendused;
1025 if (pendlen == 0) {
1026 pendsize = 0;
1027 sfree(pending);
1028 pending = NULL;
1029 }
1030 if (outlen == 0)
1031 return 1;
1032 }
1033
1034 while (outlen > 0) {
1035 fd_set readfds;
1036
1037 FD_ZERO(&readfds);
1038 FD_SET(sftp_ssh_socket, &readfds);
1039 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1040 return 0; /* doom */
1041 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1042 }
1043
1044 return 1;
1045 }
1046 int sftp_senddata(char *buf, int len)
1047 {
1048 back->send((unsigned char *) buf, len);
1049 return 1;
1050 }
1051
1052 /*
1053 * Loop through the ssh connection and authentication process.
1054 */
1055 static void ssh_sftp_init(void)
1056 {
1057 if (sftp_ssh_socket == INVALID_SOCKET)
1058 return;
1059 while (!back->sendok()) {
1060 fd_set readfds;
1061 FD_ZERO(&readfds);
1062 FD_SET(sftp_ssh_socket, &readfds);
1063 if (select(1, &readfds, NULL, NULL, NULL) < 0)
1064 return; /* doom */
1065 select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1066 }
1067 }
1068
1069 static char *password = NULL;
1070 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1071 {
1072 HANDLE hin, hout;
1073 DWORD savemode, newmode, i;
1074
1075 if (password) {
1076 static int tried_once = 0;
1077
1078 if (tried_once) {
1079 return 0;
1080 } else {
1081 strncpy(str, password, maxlen);
1082 str[maxlen - 1] = '\0';
1083 tried_once = 1;
1084 return 1;
1085 }
1086 }
1087
1088 hin = GetStdHandle(STD_INPUT_HANDLE);
1089 hout = GetStdHandle(STD_OUTPUT_HANDLE);
1090 if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1091 fprintf(stderr, "Cannot get standard input/output handles\n");
1092 exit(1);
1093 }
1094
1095 GetConsoleMode(hin, &savemode);
1096 newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1097 if (is_pw)
1098 newmode &= ~ENABLE_ECHO_INPUT;
1099 else
1100 newmode |= ENABLE_ECHO_INPUT;
1101 SetConsoleMode(hin, newmode);
1102
1103 WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1104 ReadFile(hin, str, maxlen - 1, &i, NULL);
1105
1106 SetConsoleMode(hin, savemode);
1107
1108 if ((int) i > maxlen)
1109 i = maxlen - 1;
1110 else
1111 i = i - 2;
1112 str[i] = '\0';
1113
1114 if (is_pw)
1115 WriteFile(hout, "\r\n", 2, &i, NULL);
1116
1117 return 1;
1118 }
1119
1120 /*
1121 * Initialize the Win$ock driver.
1122 */
1123 static void init_winsock(void)
1124 {
1125 WORD winsock_ver;
1126 WSADATA wsadata;
1127
1128 winsock_ver = MAKEWORD(1, 1);
1129 if (WSAStartup(winsock_ver, &wsadata)) {
1130 fprintf(stderr, "Unable to initialise WinSock");
1131 exit(1);
1132 }
1133 if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1134 fprintf(stderr, "WinSock version is incompatible with 1.1");
1135 exit(1);
1136 }
1137 }
1138
1139 /*
1140 * Short description of parameters.
1141 */
1142 static void usage(void)
1143 {
1144 printf("PuTTY Secure File Transfer (SFTP) client\n");
1145 printf("%s\n", ver);
1146 printf("Usage: psftp [options] user@host\n");
1147 printf("Options:\n");
1148 printf(" -b file use specified batchfile\n");
1149 printf(" -bc output batchfile commands\n");
1150 printf(" -be don't stop batchfile processing if errors\n");
1151 printf(" -v show verbose messages\n");
1152 printf(" -P port connect to specified port\n");
1153 printf(" -pw passw login with specified password\n");
1154 exit(1);
1155 }
1156
1157 /*
1158 * Main program. Parse arguments etc.
1159 */
1160 int main(int argc, char *argv[])
1161 {
1162 int i;
1163 int portnumber = 0;
1164 char *user, *host, *userhost, *realhost;
1165 char *err;
1166 int mode = 0;
1167 int modeflags = 0;
1168 char *batchfile = NULL;
1169
1170 flags = FLAG_STDERR;
1171 ssh_get_line = &get_line;
1172 init_winsock();
1173 sk_init();
1174
1175 userhost = user = NULL;
1176
1177 for (i = 1; i < argc; i++) {
1178 if (argv[i][0] != '-') {
1179 if (userhost)
1180 usage();
1181 else
1182 userhost = dupstr(argv[i]);
1183 } else if (strcmp(argv[i], "-v") == 0) {
1184 verbose = 1, flags |= FLAG_VERBOSE;
1185 } else if (strcmp(argv[i], "-h") == 0 ||
1186 strcmp(argv[i], "-?") == 0) {
1187 usage();
1188 } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1189 user = argv[++i];
1190 } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1191 portnumber = atoi(argv[++i]);
1192 } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1193 password = argv[++i];
1194 } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1195 mode = 1;
1196 batchfile = argv[++i];
1197 } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1198 modeflags = modeflags | 1;
1199 } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1200 modeflags = modeflags | 2;
1201 } else if (strcmp(argv[i], "--") == 0) {
1202 i++;
1203 break;
1204 } else {
1205 usage();
1206 }
1207 }
1208 argc -= i;
1209 argv += i;
1210 back = NULL;
1211
1212 if (argc > 0 || !userhost)
1213 usage();
1214
1215 /* Separate host and username */
1216 host = userhost;
1217 host = strrchr(host, '@');
1218 if (host == NULL) {
1219 host = userhost;
1220 } else {
1221 *host++ = '\0';
1222 if (user) {
1223 printf("psftp: multiple usernames specified; using \"%s\"\n",
1224 user);
1225 } else
1226 user = userhost;
1227 }
1228
1229 /* Try to load settings for this host */
1230 do_defaults(host, &cfg);
1231 if (cfg.host[0] == '\0') {
1232 /* No settings for this host; use defaults */
1233 do_defaults(NULL, &cfg);
1234 strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1235 cfg.host[sizeof(cfg.host) - 1] = '\0';
1236 cfg.port = 22;
1237 }
1238
1239 /* Set username */
1240 if (user != NULL && user[0] != '\0') {
1241 strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1242 cfg.username[sizeof(cfg.username) - 1] = '\0';
1243 }
1244 if (!cfg.username[0]) {
1245 printf("login as: ");
1246 if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1247 fprintf(stderr, "psftp: aborting\n");
1248 exit(1);
1249 } else {
1250 int len = strlen(cfg.username);
1251 if (cfg.username[len - 1] == '\n')
1252 cfg.username[len - 1] = '\0';
1253 }
1254 }
1255
1256 if (cfg.protocol != PROT_SSH)
1257 cfg.port = 22;
1258
1259 if (portnumber)
1260 cfg.port = portnumber;
1261
1262 /* SFTP uses SSH2 by default always */
1263 cfg.sshprot = 2;
1264
1265 /* Set up subsystem name. FIXME: fudge for SSH1. */
1266 strcpy(cfg.remote_cmd, "sftp");
1267 cfg.ssh_subsys = TRUE;
1268 cfg.nopty = TRUE;
1269
1270 back = &ssh_backend;
1271
1272 err = back->init(cfg.host, cfg.port, &realhost);
1273 if (err != NULL) {
1274 fprintf(stderr, "ssh_init: %s", err);
1275 return 1;
1276 }
1277 ssh_sftp_init();
1278 if (verbose && realhost != NULL)
1279 printf("Connected to %s\n", realhost);
1280
1281 do_sftp(mode, modeflags, batchfile);
1282
1283 if (back != NULL && back->socket() != NULL) {
1284 char ch;
1285 back->special(TS_EOF);
1286 sftp_recvdata(&ch, 1);
1287 }
1288 WSACleanup();
1289 random_save_seed();
1290
1291 return 0;
1292 }