]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
INCOMPATIBLE CHANGE to the SSH2 private key file format. There is
[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 #include <limits.h>
12
13 #define PUTTY_DO_GLOBALS
14 #include "putty.h"
15 #include "storage.h"
16 #include "ssh.h"
17 #include "sftp.h"
18 #include "int64.h"
19
20 /*
21  * Since SFTP is a request-response oriented protocol, it requires
22  * no buffer management: when we send data, we stop and wait for an
23  * acknowledgement _anyway_, and so we can't possibly overfill our
24  * send buffer.
25  */
26
27 /* ----------------------------------------------------------------------
28  * sftp client state.
29  */
30
31 char *pwd, *homedir;
32
33 /* ----------------------------------------------------------------------
34  * Higher-level helper functions used in commands.
35  */
36
37 /*
38  * Attempt to canonify a pathname starting from the pwd. If
39  * canonification fails, at least fall back to returning a _valid_
40  * pathname (though it may be ugly, eg /home/simon/../foobar).
41  */
42 char *canonify(char *name)
43 {
44     char *fullname, *canonname;
45
46     if (name[0] == '/') {
47         fullname = dupstr(name);
48     } else {
49         char *slash;
50         if (pwd[strlen(pwd) - 1] == '/')
51             slash = "";
52         else
53             slash = "/";
54         fullname = dupcat(pwd, slash, name, NULL);
55     }
56
57     canonname = fxp_realpath(fullname);
58
59     if (canonname) {
60         sfree(fullname);
61         return canonname;
62     } else {
63         /*
64          * Attempt number 2. Some FXP_REALPATH implementations
65          * (glibc-based ones, in particular) require the _whole_
66          * path to point to something that exists, whereas others
67          * (BSD-based) only require all but the last component to
68          * exist. So if the first call failed, we should strip off
69          * everything from the last slash onwards and try again,
70          * then put the final component back on.
71          * 
72          * Special cases:
73          * 
74          *  - if the last component is "/." or "/..", then we don't
75          *    bother trying this because there's no way it can work.
76          * 
77          *  - if the thing actually ends with a "/", we remove it
78          *    before we start. Except if the string is "/" itself
79          *    (although I can't see why we'd have got here if so,
80          *    because surely "/" would have worked the first
81          *    time?), in which case we don't bother.
82          * 
83          *  - if there's no slash in the string at all, give up in
84          *    confusion (we expect at least one because of the way
85          *    we constructed the string).
86          */
87
88         int i;
89         char *returnname;
90
91         i = strlen(fullname);
92         if (i > 2 && fullname[i - 1] == '/')
93             fullname[--i] = '\0';      /* strip trailing / unless at pos 0 */
94         while (i > 0 && fullname[--i] != '/');
95
96         /*
97          * Give up on special cases.
98          */
99         if (fullname[i] != '/' ||      /* no slash at all */
100             !strcmp(fullname + i, "/.") ||      /* ends in /. */
101             !strcmp(fullname + i, "/..") ||     /* ends in /.. */
102             !strcmp(fullname, "/")) {
103             return fullname;
104         }
105
106         /*
107          * Now i points at the slash. Deal with the final special
108          * case i==0 (ie the whole path was "/nonexistentfile").
109          */
110         fullname[i] = '\0';            /* separate the string */
111         if (i == 0) {
112             canonname = fxp_realpath("/");
113         } else {
114             canonname = fxp_realpath(fullname);
115         }
116
117         if (!canonname)
118             return fullname;           /* even that failed; give up */
119
120         /*
121          * We have a canonical name for all but the last path
122          * component. Concatenate the last component and return.
123          */
124         returnname = dupcat(canonname,
125                             canonname[strlen(canonname) - 1] ==
126                             '/' ? "" : "/", fullname + i + 1, NULL);
127         sfree(fullname);
128         sfree(canonname);
129         return returnname;
130     }
131 }
132
133 /*
134  * Return a pointer to the portion of str that comes after the last
135  * slash (or backslash or colon, if `local' is TRUE).
136  */
137 static char *stripslashes(char *str, int local)
138 {
139     char *p;
140
141     if (local) {
142         p = strchr(str, ':');
143         if (p) str = p+1;
144     }
145
146     p = strrchr(str, '/');
147     if (p) str = p+1;
148
149     if (local) {
150         p = strrchr(str, '\\');
151         if (p) str = p+1;
152     }
153
154     return str;
155 }
156
157 /* ----------------------------------------------------------------------
158  * Actual sftp commands.
159  */
160 struct sftp_command {
161     char **words;
162     int nwords, wordssize;
163     int (*obey) (struct sftp_command *);        /* returns <0 to quit */
164 };
165
166 int sftp_cmd_null(struct sftp_command *cmd)
167 {
168     return 0;
169 }
170
171 int sftp_cmd_unknown(struct sftp_command *cmd)
172 {
173     printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
174     return 0;
175 }
176
177 int sftp_cmd_quit(struct sftp_command *cmd)
178 {
179     return -1;
180 }
181
182 /*
183  * List a directory. If no arguments are given, list pwd; otherwise
184  * list the directory given in words[1].
185  */
186 static int sftp_ls_compare(const void *av, const void *bv)
187 {
188     const struct fxp_name *a = (const struct fxp_name *) av;
189     const struct fxp_name *b = (const struct fxp_name *) bv;
190     return strcmp(a->filename, b->filename);
191 }
192 int sftp_cmd_ls(struct sftp_command *cmd)
193 {
194     struct fxp_handle *dirh;
195     struct fxp_names *names;
196     struct fxp_name *ournames;
197     int nnames, namesize;
198     char *dir, *cdir;
199     int i;
200
201     if (cmd->nwords < 2)
202         dir = ".";
203     else
204         dir = cmd->words[1];
205
206     cdir = canonify(dir);
207     if (!cdir) {
208         printf("%s: %s\n", dir, fxp_error());
209         return 0;
210     }
211
212     printf("Listing directory %s\n", cdir);
213
214     dirh = fxp_opendir(cdir);
215     if (dirh == NULL) {
216         printf("Unable to open %s: %s\n", dir, fxp_error());
217     } else {
218         nnames = namesize = 0;
219         ournames = NULL;
220
221         while (1) {
222
223             names = fxp_readdir(dirh);
224             if (names == NULL) {
225                 if (fxp_error_type() == SSH_FX_EOF)
226                     break;
227                 printf("Reading directory %s: %s\n", dir, fxp_error());
228                 break;
229             }
230             if (names->nnames == 0) {
231                 fxp_free_names(names);
232                 break;
233             }
234
235             if (nnames + names->nnames >= namesize) {
236                 namesize += names->nnames + 128;
237                 ournames =
238                     srealloc(ournames, namesize * sizeof(*ournames));
239             }
240
241             for (i = 0; i < names->nnames; i++)
242                 ournames[nnames++] = names->names[i];
243
244             names->nnames = 0;         /* prevent free_names */
245             fxp_free_names(names);
246         }
247         fxp_close(dirh);
248
249         /*
250          * Now we have our filenames. Sort them by actual file
251          * name, and then output the longname parts.
252          */
253         qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
254
255         /*
256          * And print them.
257          */
258         for (i = 0; i < nnames; i++)
259             printf("%s\n", ournames[i].longname);
260     }
261
262     sfree(cdir);
263
264     return 0;
265 }
266
267 /*
268  * Change directories. We do this by canonifying the new name, then
269  * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
270  */
271 int sftp_cmd_cd(struct sftp_command *cmd)
272 {
273     struct fxp_handle *dirh;
274     char *dir;
275
276     if (cmd->nwords < 2)
277         dir = dupstr(homedir);
278     else
279         dir = canonify(cmd->words[1]);
280
281     if (!dir) {
282         printf("%s: %s\n", dir, fxp_error());
283         return 0;
284     }
285
286     dirh = fxp_opendir(dir);
287     if (!dirh) {
288         printf("Directory %s: %s\n", dir, fxp_error());
289         sfree(dir);
290         return 0;
291     }
292
293     fxp_close(dirh);
294
295     sfree(pwd);
296     pwd = dir;
297     printf("Remote directory is now %s\n", pwd);
298
299     return 0;
300 }
301
302 /*
303  * Print current directory. Easy as pie.
304  */
305 int sftp_cmd_pwd(struct sftp_command *cmd)
306 {
307     printf("Remote directory is %s\n", pwd);
308     return 0;
309 }
310
311 /*
312  * Get a file and save it at the local end. We have two very
313  * similar commands here: `get' and `reget', which differ in that
314  * `reget' checks for the existence of the destination file and
315  * starts from where a previous aborted transfer left off.
316  */
317 int sftp_general_get(struct sftp_command *cmd, int restart)
318 {
319     struct fxp_handle *fh;
320     char *fname, *outfname;
321     uint64 offset;
322     FILE *fp;
323
324     if (cmd->nwords < 2) {
325         printf("get: expects a filename\n");
326         return 0;
327     }
328
329     fname = canonify(cmd->words[1]);
330     if (!fname) {
331         printf("%s: %s\n", cmd->words[1], fxp_error());
332         return 0;
333     }
334     outfname = (cmd->nwords == 2 ?
335                 stripslashes(cmd->words[1], 0) : cmd->words[2]);
336
337     fh = fxp_open(fname, SSH_FXF_READ);
338     if (!fh) {
339         printf("%s: %s\n", fname, fxp_error());
340         sfree(fname);
341         return 0;
342     }
343
344     if (restart) {
345         fp = fopen(outfname, "rb+");
346     } else {
347         fp = fopen(outfname, "wb");
348     }
349
350     if (!fp) {
351         printf("local: unable to open %s\n", outfname);
352         fxp_close(fh);
353         sfree(fname);
354         return 0;
355     }
356
357     if (restart) {
358         long posn;
359         fseek(fp, 0L, SEEK_END);
360         posn = ftell(fp);
361         printf("reget: restarting at file position %ld\n", posn);
362         offset = uint64_make(0, posn);
363     } else {
364         offset = uint64_make(0, 0);
365     }
366
367     printf("remote:%s => local:%s\n", fname, outfname);
368
369     /*
370      * FIXME: we can use FXP_FSTAT here to get the file size, and
371      * thus put up a progress bar.
372      */
373     while (1) {
374         char buffer[4096];
375         int len;
376         int wpos, wlen;
377
378         len = fxp_read(fh, buffer, offset, sizeof(buffer));
379         if ((len == -1 && fxp_error_type() == SSH_FX_EOF) || len == 0)
380             break;
381         if (len == -1) {
382             printf("error while reading: %s\n", fxp_error());
383             break;
384         }
385
386         wpos = 0;
387         while (wpos < len) {
388             wlen = fwrite(buffer, 1, len - wpos, fp);
389             if (wlen <= 0) {
390                 printf("error while writing local file\n");
391                 break;
392             }
393             wpos += wlen;
394         }
395         if (wpos < len)                /* we had an error */
396             break;
397         offset = uint64_add32(offset, len);
398     }
399
400     fclose(fp);
401     fxp_close(fh);
402     sfree(fname);
403
404     return 0;
405 }
406 int sftp_cmd_get(struct sftp_command *cmd)
407 {
408     return sftp_general_get(cmd, 0);
409 }
410 int sftp_cmd_reget(struct sftp_command *cmd)
411 {
412     return sftp_general_get(cmd, 1);
413 }
414
415 /*
416  * Send a file and store it at the remote end. We have two very
417  * similar commands here: `put' and `reput', which differ in that
418  * `reput' checks for the existence of the destination file and
419  * starts from where a previous aborted transfer left off.
420  */
421 int sftp_general_put(struct sftp_command *cmd, int restart)
422 {
423     struct fxp_handle *fh;
424     char *fname, *origoutfname, *outfname;
425     uint64 offset;
426     FILE *fp;
427
428     if (cmd->nwords < 2) {
429         printf("put: expects a filename\n");
430         return 0;
431     }
432
433     fname = cmd->words[1];
434     origoutfname = (cmd->nwords == 2 ?
435                     stripslashes(cmd->words[1], 1) : cmd->words[2]);
436     outfname = canonify(origoutfname);
437     if (!outfname) {
438         printf("%s: %s\n", origoutfname, fxp_error());
439         return 0;
440     }
441
442     fp = fopen(fname, "rb");
443     if (!fp) {
444         printf("local: unable to open %s\n", fname);
445         sfree(outfname);
446         return 0;
447     }
448     if (restart) {
449         fh = fxp_open(outfname,
450                       SSH_FXF_WRITE);
451     } else {
452         fh = fxp_open(outfname,
453                       SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
454     }
455     if (!fh) {
456         printf("%s: %s\n", outfname, fxp_error());
457         sfree(outfname);
458         return 0;
459     }
460
461     if (restart) {
462         char decbuf[30];
463         struct fxp_attrs attrs;
464         if (!fxp_fstat(fh, &attrs)) {
465             printf("read size of %s: %s\n", outfname, fxp_error());
466             sfree(outfname);
467             return 0;
468         }
469         if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
470             printf("read size of %s: size was not given\n", outfname);
471             sfree(outfname);
472             return 0;
473         }
474         offset = attrs.size;
475         uint64_decimal(offset, decbuf);
476         printf("reput: restarting at file position %s\n", decbuf);
477         if (uint64_compare(offset, uint64_make(0, LONG_MAX)) > 0) {
478             printf("reput: remote file is larger than we can deal with\n");
479             sfree(outfname);
480             return 0;
481         }
482         if (fseek(fp, offset.lo, SEEK_SET) != 0)
483             fseek(fp, 0, SEEK_END);    /* *shrug* */
484     } else {
485         offset = uint64_make(0, 0);
486     }
487
488     printf("local:%s => remote:%s\n", fname, outfname);
489
490     /*
491      * FIXME: we can use FXP_FSTAT here to get the file size, and
492      * thus put up a progress bar.
493      */
494     while (1) {
495         char buffer[4096];
496         int len;
497
498         len = fread(buffer, 1, sizeof(buffer), fp);
499         if (len == -1) {
500             printf("error while reading local file\n");
501             break;
502         } else if (len == 0) {
503             break;
504         }
505         if (!fxp_write(fh, buffer, offset, len)) {
506             printf("error while writing: %s\n", fxp_error());
507             break;
508         }
509         offset = uint64_add32(offset, len);
510     }
511
512     fxp_close(fh);
513     fclose(fp);
514     sfree(outfname);
515
516     return 0;
517 }
518 int sftp_cmd_put(struct sftp_command *cmd)
519 {
520     return sftp_general_put(cmd, 0);
521 }
522 int sftp_cmd_reput(struct sftp_command *cmd)
523 {
524     return sftp_general_put(cmd, 1);
525 }
526
527 int sftp_cmd_mkdir(struct sftp_command *cmd)
528 {
529     char *dir;
530     int result;
531
532
533     if (cmd->nwords < 2) {
534         printf("mkdir: expects a directory\n");
535         return 0;
536     }
537
538     dir = canonify(cmd->words[1]);
539     if (!dir) {
540         printf("%s: %s\n", dir, fxp_error());
541         return 0;
542     }
543
544     result = fxp_mkdir(dir);
545     if (!result) {
546         printf("mkdir %s: %s\n", dir, fxp_error());
547         sfree(dir);
548         return 0;
549     }
550
551     sfree(dir);
552     return 0;
553 }
554
555 int sftp_cmd_rmdir(struct sftp_command *cmd)
556 {
557     char *dir;
558     int result;
559
560
561     if (cmd->nwords < 2) {
562         printf("rmdir: expects a directory\n");
563         return 0;
564     }
565
566     dir = canonify(cmd->words[1]);
567     if (!dir) {
568         printf("%s: %s\n", dir, fxp_error());
569         return 0;
570     }
571
572     result = fxp_rmdir(dir);
573     if (!result) {
574         printf("rmdir %s: %s\n", dir, fxp_error());
575         sfree(dir);
576         return 0;
577     }
578
579     sfree(dir);
580     return 0;
581 }
582
583 int sftp_cmd_rm(struct sftp_command *cmd)
584 {
585     char *fname;
586     int result;
587
588     if (cmd->nwords < 2) {
589         printf("rm: expects a filename\n");
590         return 0;
591     }
592
593     fname = canonify(cmd->words[1]);
594     if (!fname) {
595         printf("%s: %s\n", fname, fxp_error());
596         return 0;
597     }
598
599     result = fxp_remove(fname);
600     if (!result) {
601         printf("rm %s: %s\n", fname, fxp_error());
602         sfree(fname);
603         return 0;
604     }
605
606     sfree(fname);
607     return 0;
608
609 }
610
611 int sftp_cmd_mv(struct sftp_command *cmd)
612 {
613     char *srcfname, *dstfname;
614     int result;
615
616     if (cmd->nwords < 3) {
617         printf("mv: expects two filenames\n");
618         return 0;
619     }
620     srcfname = canonify(cmd->words[1]);
621     if (!srcfname) {
622         printf("%s: %s\n", srcfname, fxp_error());
623         return 0;
624     }
625
626     dstfname = canonify(cmd->words[2]);
627     if (!dstfname) {
628         printf("%s: %s\n", dstfname, fxp_error());
629         return 0;
630     }
631
632     result = fxp_rename(srcfname, dstfname);
633     if (!result) {
634         char const *error = fxp_error();
635         struct fxp_attrs attrs;
636
637         /*
638          * The move might have failed because dstfname pointed at a
639          * directory. We check this possibility now: if dstfname
640          * _is_ a directory, we re-attempt the move by appending
641          * the basename of srcfname to dstfname.
642          */
643         result = fxp_stat(dstfname, &attrs);
644         if (result &&
645             (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
646             (attrs.permissions & 0040000)) {
647             char *p;
648             char *newname, *newcanon;
649             printf("(destination %s is a directory)\n", dstfname);
650             p = srcfname + strlen(srcfname);
651             while (p > srcfname && p[-1] != '/') p--;
652             newname = dupcat(dstfname, "/", p, NULL);
653             newcanon = canonify(newname);
654             sfree(newname);
655             if (newcanon) {
656                 sfree(dstfname);
657                 dstfname = newcanon;
658                 result = fxp_rename(srcfname, dstfname);
659                 error = result ? NULL : fxp_error();
660             }
661         }
662         if (error) {
663             printf("mv %s %s: %s\n", srcfname, dstfname, error);
664             sfree(srcfname);
665             sfree(dstfname);
666             return 0;
667         }
668     }
669     printf("%s -> %s\n", srcfname, dstfname);
670
671     sfree(srcfname);
672     sfree(dstfname);
673     return 0;
674 }
675
676 int sftp_cmd_chmod(struct sftp_command *cmd)
677 {
678     char *fname, *mode;
679     int result;
680     struct fxp_attrs attrs;
681     unsigned attrs_clr, attrs_xor, oldperms, newperms;
682
683     if (cmd->nwords < 3) {
684         printf("chmod: expects a mode specifier and a filename\n");
685         return 0;
686     }
687
688     /*
689      * Attempt to parse the mode specifier in cmd->words[1]. We
690      * don't support the full horror of Unix chmod; instead we
691      * support a much simpler syntax in which the user can either
692      * specify an octal number, or a comma-separated sequence of
693      * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
694      * _only_ be omitted if the only attribute mentioned is t,
695      * since all others require a user/group/other specification.
696      * Additionally, the s attribute may not be specified for any
697      * [ugoa] specifications other than exactly u or exactly g.
698      */
699     attrs_clr = attrs_xor = 0;
700     mode = cmd->words[1];
701     if (mode[0] >= '0' && mode[0] <= '9') {
702         if (mode[strspn(mode, "01234567")]) {
703             printf("chmod: numeric file modes should"
704                    " contain digits 0-7 only\n");
705             return 0;
706         }
707         attrs_clr = 07777;
708         sscanf(mode, "%o", &attrs_xor);
709         attrs_xor &= attrs_clr;
710     } else {
711         while (*mode) {
712             char *modebegin = mode;
713             unsigned subset, perms;
714             int action;
715
716             subset = 0;
717             while (*mode && *mode != ',' &&
718                    *mode != '+' && *mode != '-' && *mode != '=') {
719                 switch (*mode) {
720                   case 'u': subset |= 04700; break; /* setuid, user perms */
721                   case 'g': subset |= 02070; break; /* setgid, group perms */
722                   case 'o': subset |= 00007; break; /* just other perms */
723                   case 'a': subset |= 06777; break; /* all of the above */
724                   default:
725                     printf("chmod: file mode '%.*s' contains unrecognised"
726                            " user/group/other specifier '%c'\n",
727                            strcspn(modebegin, ","), modebegin, *mode);
728                     return 0;
729                 }
730                 mode++;
731             }
732             if (!*mode || *mode == ',') {
733                 printf("chmod: file mode '%.*s' is incomplete\n",
734                        strcspn(modebegin, ","), modebegin);
735                 return 0;
736             }
737             action = *mode++;
738             if (!*mode || *mode == ',') {
739                 printf("chmod: file mode '%.*s' is incomplete\n",
740                        strcspn(modebegin, ","), modebegin);
741                 return 0;
742             }
743             perms = 0;
744             while (*mode && *mode != ',') {
745                 switch (*mode) {
746                   case 'r': perms |= 00444; break;
747                   case 'w': perms |= 00222; break;
748                   case 'x': perms |= 00111; break;
749                   case 't': perms |= 01000; subset |= 01000; break;
750                   case 's':
751                     if ((subset & 06777) != 04700 &&
752                         (subset & 06777) != 02070) {
753                         printf("chmod: file mode '%.*s': set[ug]id bit should"
754                                " be used with exactly one of u or g only\n",
755                                strcspn(modebegin, ","), modebegin);
756                         return 0;
757                     }
758                     perms |= 06000;
759                     break;
760                   default:
761                     printf("chmod: file mode '%.*s' contains unrecognised"
762                            " permission specifier '%c'\n",
763                            strcspn(modebegin, ","), modebegin, *mode);
764                     return 0;
765                 }
766                 mode++;
767             }
768             if (!(subset & 06777) && (perms &~ subset)) {
769                 printf("chmod: file mode '%.*s' contains no user/group/other"
770                        " specifier and permissions other than 't' \n",
771                        strcspn(modebegin, ","), modebegin, *mode);
772                 return 0;
773             }
774             perms &= subset;
775             switch (action) {
776               case '+':
777                 attrs_clr |= perms;
778                 attrs_xor |= perms;
779                 break;
780               case '-':
781                 attrs_clr |= perms;
782                 attrs_xor &= ~perms;
783                 break;
784               case '=':
785                 attrs_clr |= subset;
786                 attrs_xor |= perms;
787                 break;
788             }
789             if (*mode) mode++;         /* eat comma */
790         }
791     }
792
793     fname = canonify(cmd->words[2]);
794     if (!fname) {
795         printf("%s: %s\n", fname, fxp_error());
796         return 0;
797     }
798
799     result = fxp_stat(fname, &attrs);
800     if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
801         printf("get attrs for %s: %s\n", fname,
802                result ? "file permissions not provided" : fxp_error());
803         sfree(fname);
804         return 0;
805     }
806
807     attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;   /* perms _only_ */
808     oldperms = attrs.permissions & 07777;
809     attrs.permissions &= ~attrs_clr;
810     attrs.permissions ^= attrs_xor;
811     newperms = attrs.permissions & 07777;
812
813     result = fxp_setstat(fname, attrs);
814
815     if (!result) {
816         printf("set attrs for %s: %s\n", fname, fxp_error());
817         sfree(fname);
818         return 0;
819     }
820
821     printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
822
823     sfree(fname);
824     return 0;
825 }
826
827 static int sftp_cmd_help(struct sftp_command *cmd);
828
829 static struct sftp_cmd_lookup {
830     char *name;
831     /*
832      * For help purposes, there are two kinds of command:
833      * 
834      *  - primary commands, in which `longhelp' is non-NULL. In
835      *    this case `shorthelp' is descriptive text, and `longhelp'
836      *    is longer descriptive text intended to be printed after
837      *    the command name.
838      * 
839      *  - alias commands, in which `longhelp' is NULL. In this case
840      *    `shorthelp' is the name of a primary command, which
841      *    contains the help that should double up for this command.
842      */
843     char *shorthelp;
844     char *longhelp;
845     int (*obey) (struct sftp_command *);
846 } sftp_lookup[] = {
847     /*
848      * List of sftp commands. This is binary-searched so it MUST be
849      * in ASCII order.
850      */
851     {
852         "bye", "finish your SFTP session",
853             "\n"
854             "  Terminates your SFTP session and quits the PSFTP program.\n",
855             sftp_cmd_quit
856     },
857     {
858         "cd", "change your remote working directory",
859             " [ <New working directory> ]\n"
860             "  Change the remote working directory for your SFTP session.\n"
861             "  If a new working directory is not supplied, you will be\n"
862             "  returned to your home directory.\n",
863             sftp_cmd_cd
864     },
865     {
866         "chmod", "change file permissions and modes",
867             " ( <octal-digits> | <modifiers> ) <filename>\n"
868             "  Change the file permissions on a file or directory.\n"
869             "  <octal-digits> can be any octal Unix permission specifier.\n"
870             "  Alternatively, <modifiers> can include:\n"
871             "    u+r     make file readable by owning user\n"
872             "    u+w     make file writable by owning user\n"
873             "    u+x     make file executable by owning user\n"
874             "    u-r     make file not readable by owning user\n"
875             "    [also u-w, u-x]\n"
876             "    g+r     make file readable by members of owning group\n"
877             "    [also g+w, g+x, g-r, g-w, g-x]\n"
878             "    o+r     make file readable by all other users\n"
879             "    [also o+w, o+x, o-r, o-w, o-x]\n"
880             "    a+r     make file readable by absolutely everybody\n"
881             "    [also a+w, a+x, a-r, a-w, a-x]\n"
882             "    u+s     enable the Unix set-user-ID bit\n"
883             "    u-s     disable the Unix set-user-ID bit\n"
884             "    g+s     enable the Unix set-group-ID bit\n"
885             "    g-s     disable the Unix set-group-ID bit\n"
886             "    +t      enable the Unix \"sticky bit\"\n"
887             "  You can give more than one modifier for the same user (\"g-rwx\"), and\n"
888             "  more than one user for the same modifier (\"ug+w\"). You can\n"
889             "  use commas to separate different modifiers (\"u+rwx,g+s\").\n",
890             sftp_cmd_chmod
891     },
892     {
893         "del", "delete a file",
894             " <filename>\n"
895             "  Delete a file.\n",
896             sftp_cmd_rm
897     },
898     {
899         "delete", "delete a file",
900             "\n"
901             "  Delete a file.\n",
902             sftp_cmd_rm
903     },
904     {
905         "dir", "list contents of a remote directory",
906             " [ <directory-name> ]\n"
907             "  List the contents of a specified directory on the server.\n"
908             "  If <directory-name> is not given, the current working directory\n"
909             "  will be listed.\n",
910             sftp_cmd_ls
911     },
912     {
913         "exit", "bye", NULL, sftp_cmd_quit
914     },
915     {
916         "get", "download a file from the server to your local machine",
917             " <filename> [ <local-filename> ]\n"
918             "  Downloads a file on the server and stores it locally under\n"
919             "  the same name, or under a different one if you supply the\n"
920             "  argument <local-filename>.\n",
921             sftp_cmd_get
922     },
923     {
924         "help", "give help",
925             " [ <command> [ <command> ... ] ]\n"
926             "  Give general help if no commands are specified.\n"
927             "  If one or more commands are specified, give specific help on\n"
928             "  those particular commands.\n",
929             sftp_cmd_help
930     },
931     {
932         "ls", "dir", NULL,
933             sftp_cmd_ls
934     },
935     {
936         "mkdir", "create a directory on the remote server",
937             " <directory-name>\n"
938             "  Creates a directory with the given name on the server.\n",
939             sftp_cmd_mkdir
940     },
941     {
942         "mv", "move or rename a file on the remote server",
943             " <source-filename> <destination-filename>\n"
944             "  Moves or renames the file <source-filename> on the server,\n"
945             "  so that it is accessible under the name <destination-filename>.\n",
946             sftp_cmd_mv
947     },
948     {
949         "put", "upload a file from your local machine to the server",
950             " <filename> [ <remote-filename> ]\n"
951             "  Uploads a file to the server and stores it there under\n"
952             "  the same name, or under a different one if you supply the\n"
953             "  argument <remote-filename>.\n",
954             sftp_cmd_put
955     },
956     {
957         "pwd", "print your remote working directory",
958             "\n"
959             "  Print the current remote working directory for your SFTP session.\n",
960             sftp_cmd_pwd
961     },
962     {
963         "quit", "bye", NULL,
964             sftp_cmd_quit
965     },
966     {
967         "reget", "continue downloading a file",
968             " <filename> [ <local-filename> ]\n"
969             "  Works exactly like the \"get\" command, but the local file\n"
970             "  must already exist. The download will begin at the end of the\n"
971             "  file. This is for resuming a download that was interrupted.\n",
972             sftp_cmd_reget
973     },
974     {
975         "ren", "mv", NULL,
976             sftp_cmd_mv
977     },
978     {
979         "rename", "mv", NULL,
980             sftp_cmd_mv
981     },
982     {
983         "reput", "continue uploading a file",
984             " <filename> [ <remote-filename> ]\n"
985             "  Works exactly like the \"put\" command, but the remote file\n"
986             "  must already exist. The upload will begin at the end of the\n"
987             "  file. This is for resuming an upload that was interrupted.\n",
988             sftp_cmd_reput
989     },
990     {
991         "rm", "del", NULL,
992             sftp_cmd_rm
993     },
994     {
995         "rmdir", "remove a directory on the remote server",
996             " <directory-name>\n"
997             "  Removes the directory with the given name on the server.\n"
998             "  The directory will not be removed unless it is empty.\n",
999             sftp_cmd_rmdir
1000     }
1001 };
1002
1003 const struct sftp_cmd_lookup *lookup_command(char *name)
1004 {
1005     int i, j, k, cmp;
1006
1007     i = -1;
1008     j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
1009     while (j - i > 1) {
1010         k = (j + i) / 2;
1011         cmp = strcmp(name, sftp_lookup[k].name);
1012         if (cmp < 0)
1013             j = k;
1014         else if (cmp > 0)
1015             i = k;
1016         else {
1017             return &sftp_lookup[k];
1018         }
1019     }
1020     return NULL;
1021 }
1022
1023 static int sftp_cmd_help(struct sftp_command *cmd)
1024 {
1025     int i;
1026     if (cmd->nwords == 1) {
1027         /*
1028          * Give short help on each command.
1029          */
1030         int maxlen;
1031         maxlen = 0;
1032         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1033             int len = strlen(sftp_lookup[i].name);
1034             if (maxlen < len)
1035                 maxlen = len;
1036         }
1037         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1038             const struct sftp_cmd_lookup *lookup;
1039             lookup = &sftp_lookup[i];
1040             printf("%-*s", maxlen+2, lookup->name);
1041             if (lookup->longhelp == NULL)
1042                 lookup = lookup_command(lookup->shorthelp);
1043             printf("%s\n", lookup->shorthelp);
1044         }
1045     } else {
1046         /*
1047          * Give long help on specific commands.
1048          */
1049         for (i = 1; i < cmd->nwords; i++) {
1050             const struct sftp_cmd_lookup *lookup;
1051             lookup = lookup_command(cmd->words[i]);
1052             if (!lookup) {
1053                 printf("help: %s: command not found\n", cmd->words[i]);
1054             } else {
1055                 printf("%s", lookup->name);
1056                 if (lookup->longhelp == NULL)
1057                     lookup = lookup_command(lookup->shorthelp);
1058                 printf("%s", lookup->longhelp);
1059             }
1060         }
1061     }
1062     return 0;
1063 }
1064
1065 /* ----------------------------------------------------------------------
1066  * Command line reading and parsing.
1067  */
1068 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1069 {
1070     char *line;
1071     int linelen, linesize;
1072     struct sftp_command *cmd;
1073     char *p, *q, *r;
1074     int quoting;
1075
1076         if ((mode == 0) || (modeflags & 1)) {
1077             printf("psftp> ");
1078         }
1079     fflush(stdout);
1080
1081     cmd = smalloc(sizeof(struct sftp_command));
1082     cmd->words = NULL;
1083     cmd->nwords = 0;
1084     cmd->wordssize = 0;
1085
1086     line = NULL;
1087     linesize = linelen = 0;
1088     while (1) {
1089         int len;
1090         char *ret;
1091
1092         linesize += 512;
1093         line = srealloc(line, linesize);
1094         ret = fgets(line + linelen, linesize - linelen, fp);
1095         if (modeflags & 1) {
1096                 printf("%s", ret);
1097         }
1098
1099         if (!ret || (linelen == 0 && line[0] == '\0')) {
1100             cmd->obey = sftp_cmd_quit;
1101             printf("quit\n");
1102             return cmd;                /* eof */
1103         }
1104         len = linelen + strlen(line + linelen);
1105         linelen += len;
1106         if (line[linelen - 1] == '\n') {
1107             linelen--;
1108             line[linelen] = '\0';
1109             break;
1110         }
1111     }
1112
1113     /*
1114      * Parse the command line into words. The syntax is:
1115      *  - double quotes are removed, but cause spaces within to be
1116      *    treated as non-separating.
1117      *  - a double-doublequote pair is a literal double quote, inside
1118      *    _or_ outside quotes. Like this:
1119      * 
1120      *      firstword "second word" "this has ""quotes"" in" sodoes""this""
1121      * 
1122      * becomes
1123      * 
1124      *      >firstword<
1125      *      >second word<
1126      *      >this has "quotes" in<
1127      *      >sodoes"this"<
1128      */
1129     p = line;
1130     while (*p) {
1131         /* skip whitespace */
1132         while (*p && (*p == ' ' || *p == '\t'))
1133             p++;
1134         /* mark start of word */
1135         q = r = p;                     /* q sits at start, r writes word */
1136         quoting = 0;
1137         while (*p) {
1138             if (!quoting && (*p == ' ' || *p == '\t'))
1139                 break;                 /* reached end of word */
1140             else if (*p == '"' && p[1] == '"')
1141                 p += 2, *r++ = '"';    /* a literal quote */
1142             else if (*p == '"')
1143                 p++, quoting = !quoting;
1144             else
1145                 *r++ = *p++;
1146         }
1147         if (*p)
1148             p++;                       /* skip over the whitespace */
1149         *r = '\0';
1150         if (cmd->nwords >= cmd->wordssize) {
1151             cmd->wordssize = cmd->nwords + 16;
1152             cmd->words =
1153                 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1154         }
1155         cmd->words[cmd->nwords++] = q;
1156     }
1157
1158     /*
1159      * Now parse the first word and assign a function.
1160      */
1161
1162     if (cmd->nwords == 0)
1163         cmd->obey = sftp_cmd_null;
1164     else {
1165         const struct sftp_cmd_lookup *lookup;
1166         lookup = lookup_command(cmd->words[0]);
1167         if (!lookup)
1168             cmd->obey = sftp_cmd_unknown;
1169         else
1170             cmd->obey = lookup->obey;
1171     }
1172
1173     return cmd;
1174 }
1175
1176 void do_sftp(int mode, int modeflags, char *batchfile)
1177 {
1178     FILE *fp;
1179
1180     /*
1181      * Do protocol initialisation. 
1182      */
1183     if (!fxp_init()) {
1184         fprintf(stderr,
1185                 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1186         return;
1187     }
1188
1189     /*
1190      * Find out where our home directory is.
1191      */
1192     homedir = fxp_realpath(".");
1193     if (!homedir) {
1194         fprintf(stderr,
1195                 "Warning: failed to resolve home directory: %s\n",
1196                 fxp_error());
1197         homedir = dupstr(".");
1198     } else {
1199         printf("Remote working directory is %s\n", homedir);
1200     }
1201     pwd = dupstr(homedir);
1202
1203     /*
1204      * Batch mode?
1205      */
1206     if (mode == 0) {
1207
1208         /* ------------------------------------------------------------------
1209          * Now we're ready to do Real Stuff.
1210          */
1211         while (1) {
1212         struct sftp_command *cmd;
1213         cmd = sftp_getcmd(stdin, 0, 0);
1214         if (!cmd)
1215             break;
1216                 if (cmd->obey(cmd) < 0)
1217                     break;
1218         }
1219     } else {
1220         fp = fopen(batchfile, "r");
1221         if (!fp) {
1222             printf("Fatal: unable to open %s\n", batchfile);
1223             return;
1224         }
1225         while (1) {
1226             struct sftp_command *cmd;
1227             cmd = sftp_getcmd(fp, mode, modeflags);
1228             if (!cmd)
1229                 break;
1230             if (cmd->obey(cmd) < 0)
1231                 break;
1232             if (fxp_error() != NULL) {
1233                 if (!(modeflags & 2))
1234                     break;
1235             }
1236         }
1237         fclose(fp);
1238
1239     }
1240 }
1241
1242 /* ----------------------------------------------------------------------
1243  * Dirty bits: integration with PuTTY.
1244  */
1245
1246 static int verbose = 0;
1247
1248 void verify_ssh_host_key(char *host, int port, char *keytype,
1249                          char *keystr, char *fingerprint)
1250 {
1251     int ret;
1252     HANDLE hin;
1253     DWORD savemode, i;
1254
1255     static const char absentmsg[] =
1256         "The server's host key is not cached in the registry. You\n"
1257         "have no guarantee that the server is the computer you\n"
1258         "think it is.\n"
1259         "The server's key fingerprint is:\n"
1260         "%s\n"
1261         "If you trust this host, enter \"y\" to add the key to\n"
1262         "PuTTY's cache and carry on connecting.\n"
1263         "If you want to carry on connecting just once, without\n"
1264         "adding the key to the cache, enter \"n\".\n"
1265         "If you do not trust this host, press Return to abandon the\n"
1266         "connection.\n"
1267         "Store key in cache? (y/n) ";
1268
1269     static const char wrongmsg[] =
1270         "WARNING - POTENTIAL SECURITY BREACH!\n"
1271         "The server's host key does not match the one PuTTY has\n"
1272         "cached in the registry. This means that either the\n"
1273         "server administrator has changed the host key, or you\n"
1274         "have actually connected to another computer pretending\n"
1275         "to be the server.\n"
1276         "The new key fingerprint is:\n"
1277         "%s\n"
1278         "If you were expecting this change and trust the new key,\n"
1279         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1280         "If you want to carry on connecting but without updating\n"
1281         "the cache, enter \"n\".\n"
1282         "If you want to abandon the connection completely, press\n"
1283         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1284         "safe choice.\n"
1285         "Update cached key? (y/n, Return cancels connection) ";
1286
1287     static const char abandoned[] = "Connection abandoned.\n";
1288
1289     char line[32];
1290
1291     /*
1292      * Verify the key against the registry.
1293      */
1294     ret = verify_host_key(host, port, keytype, keystr);
1295
1296     if (ret == 0)                      /* success - key matched OK */
1297         return;
1298
1299     if (ret == 2) {                    /* key was different */
1300         fprintf(stderr, wrongmsg, fingerprint);
1301         fflush(stderr);
1302     }
1303     if (ret == 1) {                    /* key was absent */
1304         fprintf(stderr, absentmsg, fingerprint);
1305         fflush(stderr);
1306     }
1307
1308     hin = GetStdHandle(STD_INPUT_HANDLE);
1309     GetConsoleMode(hin, &savemode);
1310     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1311                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1312     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1313     SetConsoleMode(hin, savemode);
1314
1315     if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1316         if (line[0] == 'y' || line[0] == 'Y')
1317             store_host_key(host, port, keytype, keystr);
1318     } else {
1319         fprintf(stderr, abandoned);
1320         exit(0);
1321     }
1322 }
1323
1324 /*
1325  * Ask whether the selected cipher is acceptable (since it was
1326  * below the configured 'warn' threshold).
1327  * cs: 0 = both ways, 1 = client->server, 2 = server->client
1328  */
1329 void askcipher(char *ciphername, int cs)
1330 {
1331     HANDLE hin;
1332     DWORD savemode, i;
1333
1334     static const char msg[] =
1335         "The first %scipher supported by the server is\n"
1336         "%s, which is below the configured warning threshold.\n"
1337         "Continue with connection? (y/n) ";
1338     static const char abandoned[] = "Connection abandoned.\n";
1339
1340     char line[32];
1341
1342     fprintf(stderr, msg,
1343             (cs == 0) ? "" :
1344             (cs == 1) ? "client-to-server " :
1345                         "server-to-client ",
1346             ciphername);
1347     fflush(stderr);
1348
1349     hin = GetStdHandle(STD_INPUT_HANDLE);
1350     GetConsoleMode(hin, &savemode);
1351     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1352                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1353     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1354     SetConsoleMode(hin, savemode);
1355
1356     if (line[0] == 'y' || line[0] == 'Y') {
1357         return;
1358     } else {
1359         fprintf(stderr, abandoned);
1360         exit(0);
1361     }
1362 }
1363
1364 /*
1365  * Warn about the obsolescent key file format.
1366  */
1367 void old_keyfile_warning(void)
1368 {
1369     static const char message[] =
1370         "You are loading an SSH 2 private key which has an\n"
1371         "old version of the file format. This means your key\n"
1372         "file is not fully tamperproof. Future versions of\n"
1373         "PuTTY may stop supporting this private key format,\n"
1374         "so we recommend you convert your key to the new\n"
1375         "format.\n"
1376         "\n"
1377         "Once the key is loaded into PuTTYgen, you can perform\n"
1378         "this conversion simply by saving it again.\n";
1379
1380     fputs(message, stderr);
1381 }
1382
1383 /*
1384  *  Print an error message and perform a fatal exit.
1385  */
1386 void fatalbox(char *fmt, ...)
1387 {
1388     char str[0x100];                   /* Make the size big enough */
1389     va_list ap;
1390     va_start(ap, fmt);
1391     strcpy(str, "Fatal:");
1392     vsprintf(str + strlen(str), fmt, ap);
1393     va_end(ap);
1394     strcat(str, "\n");
1395     fprintf(stderr, str);
1396
1397     exit(1);
1398 }
1399 void connection_fatal(char *fmt, ...)
1400 {
1401     char str[0x100];                   /* Make the size big enough */
1402     va_list ap;
1403     va_start(ap, fmt);
1404     strcpy(str, "Fatal:");
1405     vsprintf(str + strlen(str), fmt, ap);
1406     va_end(ap);
1407     strcat(str, "\n");
1408     fprintf(stderr, str);
1409
1410     exit(1);
1411 }
1412
1413 void logevent(char *string)
1414 {
1415 }
1416
1417 void ldisc_send(char *buf, int len, int interactive)
1418 {
1419     /*
1420      * This is only here because of the calls to ldisc_send(NULL,
1421      * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1422      * ldisc as an ldisc. So if we get called with any real data, I
1423      * want to know about it.
1424      */
1425     assert(len == 0);
1426 }
1427
1428 /*
1429  * Be told what socket we're supposed to be using.
1430  */
1431 static SOCKET sftp_ssh_socket;
1432 char *do_select(SOCKET skt, int startup)
1433 {
1434     if (startup)
1435         sftp_ssh_socket = skt;
1436     else
1437         sftp_ssh_socket = INVALID_SOCKET;
1438     return NULL;
1439 }
1440 extern int select_result(WPARAM, LPARAM);
1441
1442 /*
1443  * Receive a block of data from the SSH link. Block until all data
1444  * is available.
1445  *
1446  * To do this, we repeatedly call the SSH protocol module, with our
1447  * own trap in from_backend() to catch the data that comes back. We
1448  * do this until we have enough data.
1449  */
1450
1451 static unsigned char *outptr;          /* where to put the data */
1452 static unsigned outlen;                /* how much data required */
1453 static unsigned char *pending = NULL;  /* any spare data */
1454 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */
1455 int from_backend(int is_stderr, char *data, int datalen)
1456 {
1457     unsigned char *p = (unsigned char *) data;
1458     unsigned len = (unsigned) datalen;
1459
1460     /*
1461      * stderr data is just spouted to local stderr and otherwise
1462      * ignored.
1463      */
1464     if (is_stderr) {
1465         fwrite(data, 1, len, stderr);
1466         return 0;
1467     }
1468
1469     /*
1470      * If this is before the real session begins, just return.
1471      */
1472     if (!outptr)
1473         return 0;
1474
1475     if (outlen > 0) {
1476         unsigned used = outlen;
1477         if (used > len)
1478             used = len;
1479         memcpy(outptr, p, used);
1480         outptr += used;
1481         outlen -= used;
1482         p += used;
1483         len -= used;
1484     }
1485
1486     if (len > 0) {
1487         if (pendsize < pendlen + len) {
1488             pendsize = pendlen + len + 4096;
1489             pending = (pending ? srealloc(pending, pendsize) :
1490                        smalloc(pendsize));
1491             if (!pending)
1492                 fatalbox("Out of memory");
1493         }
1494         memcpy(pending + pendlen, p, len);
1495         pendlen += len;
1496     }
1497
1498     return 0;
1499 }
1500 int sftp_recvdata(char *buf, int len)
1501 {
1502     outptr = (unsigned char *) buf;
1503     outlen = len;
1504
1505     /*
1506      * See if the pending-input block contains some of what we
1507      * need.
1508      */
1509     if (pendlen > 0) {
1510         unsigned pendused = pendlen;
1511         if (pendused > outlen)
1512             pendused = outlen;
1513         memcpy(outptr, pending, pendused);
1514         memmove(pending, pending + pendused, pendlen - pendused);
1515         outptr += pendused;
1516         outlen -= pendused;
1517         pendlen -= pendused;
1518         if (pendlen == 0) {
1519             pendsize = 0;
1520             sfree(pending);
1521             pending = NULL;
1522         }
1523         if (outlen == 0)
1524             return 1;
1525     }
1526
1527     while (outlen > 0) {
1528         fd_set readfds;
1529
1530         FD_ZERO(&readfds);
1531         FD_SET(sftp_ssh_socket, &readfds);
1532         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1533             return 0;                  /* doom */
1534         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1535     }
1536
1537     return 1;
1538 }
1539 int sftp_senddata(char *buf, int len)
1540 {
1541     back->send((unsigned char *) buf, len);
1542     return 1;
1543 }
1544
1545 /*
1546  * Loop through the ssh connection and authentication process.
1547  */
1548 static void ssh_sftp_init(void)
1549 {
1550     if (sftp_ssh_socket == INVALID_SOCKET)
1551         return;
1552     while (!back->sendok()) {
1553         fd_set readfds;
1554         FD_ZERO(&readfds);
1555         FD_SET(sftp_ssh_socket, &readfds);
1556         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1557             return;                    /* doom */
1558         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1559     }
1560 }
1561
1562 static char *password = NULL;
1563 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1564 {
1565     HANDLE hin, hout;
1566     DWORD savemode, newmode, i;
1567
1568     if (password) {
1569         static int tried_once = 0;
1570
1571         if (tried_once) {
1572             return 0;
1573         } else {
1574             strncpy(str, password, maxlen);
1575             str[maxlen - 1] = '\0';
1576             tried_once = 1;
1577             return 1;
1578         }
1579     }
1580
1581     hin = GetStdHandle(STD_INPUT_HANDLE);
1582     hout = GetStdHandle(STD_OUTPUT_HANDLE);
1583     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1584         fprintf(stderr, "Cannot get standard input/output handles\n");
1585         exit(1);
1586     }
1587
1588     GetConsoleMode(hin, &savemode);
1589     newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1590     if (is_pw)
1591         newmode &= ~ENABLE_ECHO_INPUT;
1592     else
1593         newmode |= ENABLE_ECHO_INPUT;
1594     SetConsoleMode(hin, newmode);
1595
1596     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1597     ReadFile(hin, str, maxlen - 1, &i, NULL);
1598
1599     SetConsoleMode(hin, savemode);
1600
1601     if ((int) i > maxlen)
1602         i = maxlen - 1;
1603     else
1604         i = i - 2;
1605     str[i] = '\0';
1606
1607     if (is_pw)
1608         WriteFile(hout, "\r\n", 2, &i, NULL);
1609
1610     return 1;
1611 }
1612
1613 /*
1614  *  Initialize the Win$ock driver.
1615  */
1616 static void init_winsock(void)
1617 {
1618     WORD winsock_ver;
1619     WSADATA wsadata;
1620
1621     winsock_ver = MAKEWORD(1, 1);
1622     if (WSAStartup(winsock_ver, &wsadata)) {
1623         fprintf(stderr, "Unable to initialise WinSock");
1624         exit(1);
1625     }
1626     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1627         fprintf(stderr, "WinSock version is incompatible with 1.1");
1628         exit(1);
1629     }
1630 }
1631
1632 /*
1633  *  Short description of parameters.
1634  */
1635 static void usage(void)
1636 {
1637     printf("PuTTY Secure File Transfer (SFTP) client\n");
1638     printf("%s\n", ver);
1639     printf("Usage: psftp [options] user@host\n");
1640     printf("Options:\n");
1641     printf("  -b file   use specified batchfile\n");
1642     printf("  -bc       output batchfile commands\n");
1643     printf("  -be       don't stop batchfile processing if errors\n");
1644     printf("  -v        show verbose messages\n");
1645     printf("  -P port   connect to specified port\n");
1646     printf("  -pw passw login with specified password\n");
1647     exit(1);
1648 }
1649
1650 /*
1651  * Main program. Parse arguments etc.
1652  */
1653 int main(int argc, char *argv[])
1654 {
1655     int i;
1656     int portnumber = 0;
1657     char *user, *host, *userhost, *realhost;
1658     char *err;
1659     int mode = 0;
1660     int modeflags = 0;
1661     char *batchfile = NULL;
1662
1663     flags = FLAG_STDERR | FLAG_INTERACTIVE;
1664     ssh_get_line = &get_line;
1665     init_winsock();
1666     sk_init();
1667
1668     userhost = user = NULL;
1669
1670     for (i = 1; i < argc; i++) {
1671         if (argv[i][0] != '-') {
1672             if (userhost)
1673                 usage();
1674             else
1675                 userhost = dupstr(argv[i]);
1676         } else if (strcmp(argv[i], "-v") == 0) {
1677             verbose = 1, flags |= FLAG_VERBOSE;
1678         } else if (strcmp(argv[i], "-h") == 0 ||
1679                    strcmp(argv[i], "-?") == 0) {
1680             usage();
1681         } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1682             user = argv[++i];
1683         } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1684             portnumber = atoi(argv[++i]);
1685         } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1686             password = argv[++i];
1687     } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1688             mode = 1;
1689         batchfile = argv[++i];
1690     } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1691             modeflags = modeflags | 1;
1692     } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1693             modeflags = modeflags | 2;
1694         } else if (strcmp(argv[i], "--") == 0) {
1695             i++;
1696             break;
1697         } else {
1698             usage();
1699         }
1700     }
1701     argc -= i;
1702     argv += i;
1703     back = NULL;
1704
1705     if (argc > 0 || !userhost)
1706         usage();
1707
1708     /* Separate host and username */
1709     host = userhost;
1710     host = strrchr(host, '@');
1711     if (host == NULL) {
1712         host = userhost;
1713     } else {
1714         *host++ = '\0';
1715         if (user) {
1716             printf("psftp: multiple usernames specified; using \"%s\"\n",
1717                    user);
1718         } else
1719             user = userhost;
1720     }
1721
1722     /* Try to load settings for this host */
1723     do_defaults(host, &cfg);
1724     if (cfg.host[0] == '\0') {
1725         /* No settings for this host; use defaults */
1726         do_defaults(NULL, &cfg);
1727         strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1728         cfg.host[sizeof(cfg.host) - 1] = '\0';
1729         cfg.port = 22;
1730     }
1731
1732     /*
1733      * Trim leading whitespace off the hostname if it's there.
1734      */
1735     {
1736         int space = strspn(cfg.host, " \t");
1737         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1738     }
1739
1740     /* See if host is of the form user@host */
1741     if (cfg.host[0] != '\0') {
1742         char *atsign = strchr(cfg.host, '@');
1743         /* Make sure we're not overflowing the user field */
1744         if (atsign) {
1745             if (atsign - cfg.host < sizeof cfg.username) {
1746                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1747                 cfg.username[atsign - cfg.host] = '\0';
1748             }
1749             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1750         }
1751     }
1752
1753     /*
1754      * Trim a colon suffix off the hostname if it's there.
1755      */
1756     cfg.host[strcspn(cfg.host, ":")] = '\0';
1757
1758     /* Set username */
1759     if (user != NULL && user[0] != '\0') {
1760         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1761         cfg.username[sizeof(cfg.username) - 1] = '\0';
1762     }
1763     if (!cfg.username[0]) {
1764         printf("login as: ");
1765         if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1766             fprintf(stderr, "psftp: aborting\n");
1767             exit(1);
1768         } else {
1769             int len = strlen(cfg.username);
1770             if (cfg.username[len - 1] == '\n')
1771                 cfg.username[len - 1] = '\0';
1772         }
1773     }
1774
1775     if (cfg.protocol != PROT_SSH)
1776         cfg.port = 22;
1777
1778     if (portnumber)
1779         cfg.port = portnumber;
1780
1781     /* SFTP uses SSH2 by default always */
1782     cfg.sshprot = 2;
1783
1784     /*
1785      * Disable scary things which shouldn't be enabled for simple
1786      * things like SCP and SFTP: agent forwarding, port forwarding,
1787      * X forwarding.
1788      */
1789     cfg.x11_forward = 0;
1790     cfg.agentfwd = 0;
1791     cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1792
1793     /* Set up subsystem name. */
1794     strcpy(cfg.remote_cmd, "sftp");
1795     cfg.ssh_subsys = TRUE;
1796     cfg.nopty = TRUE;
1797
1798     /*
1799      * Set up fallback option, for SSH1 servers or servers with the
1800      * sftp subsystem not enabled but the server binary installed
1801      * in the usual place. We only support fallback on Unix
1802      * systems, and we use a kludgy piece of shellery which should
1803      * try to find sftp-server in various places (the obvious
1804      * systemwide spots /usr/lib and /usr/local/lib, and then the
1805      * user's PATH) and finally give up.
1806      * 
1807      *   test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1808      *   test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1809      *   exec sftp-server
1810      * 
1811      * the idea being that this will attempt to use either of the
1812      * obvious pathnames and then give up, and when it does give up
1813      * it will print the preferred pathname in the error messages.
1814      */
1815     cfg.remote_cmd_ptr2 =
1816         "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1817         "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1818         "exec sftp-server";
1819     cfg.ssh_subsys2 = FALSE;
1820
1821     back = &ssh_backend;
1822
1823     err = back->init(cfg.host, cfg.port, &realhost);
1824     if (err != NULL) {
1825         fprintf(stderr, "ssh_init: %s", err);
1826         return 1;
1827     }
1828     ssh_sftp_init();
1829     if (verbose && realhost != NULL)
1830         printf("Connected to %s\n", realhost);
1831
1832     do_sftp(mode, modeflags, batchfile);
1833
1834     if (back != NULL && back->socket() != NULL) {
1835         char ch;
1836         back->special(TS_EOF);
1837         sftp_recvdata(&ch, 1);
1838     }
1839     WSACleanup();
1840     random_save_seed();
1841
1842     return 0;
1843 }