]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
Extensive changes that _should_ fix the socket buffering problems,
[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  *  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 }