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