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