]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
Jacob's patch for a drag-list to select SSH ciphers. Heavily hacked
[PuTTY.git] / 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 }