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