]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
Fix trivial problems with PSFTP batch mode
[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_help(struct sftp_command *cmd);
908
909 static struct sftp_cmd_lookup {
910     char *name;
911     /*
912      * For help purposes, there are two kinds of command:
913      * 
914      *  - primary commands, in which `longhelp' is non-NULL. In
915      *    this case `shorthelp' is descriptive text, and `longhelp'
916      *    is longer descriptive text intended to be printed after
917      *    the command name.
918      * 
919      *  - alias commands, in which `longhelp' is NULL. In this case
920      *    `shorthelp' is the name of a primary command, which
921      *    contains the help that should double up for this command.
922      */
923     char *shorthelp;
924     char *longhelp;
925     int (*obey) (struct sftp_command *);
926 } sftp_lookup[] = {
927     /*
928      * List of sftp commands. This is binary-searched so it MUST be
929      * in ASCII order.
930      */
931     {
932         "bye", "finish your SFTP session",
933             "\n"
934             "  Terminates your SFTP session and quits the PSFTP program.\n",
935             sftp_cmd_quit
936     },
937     {
938         "cd", "change your remote working directory",
939             " [ <New working directory> ]\n"
940             "  Change the remote working directory for your SFTP session.\n"
941             "  If a new working directory is not supplied, you will be\n"
942             "  returned to your home directory.\n",
943             sftp_cmd_cd
944     },
945     {
946         "chmod", "change file permissions and modes",
947             " ( <octal-digits> | <modifiers> ) <filename>\n"
948             "  Change the file permissions on a file or directory.\n"
949             "  <octal-digits> can be any octal Unix permission specifier.\n"
950             "  Alternatively, <modifiers> can include:\n"
951             "    u+r     make file readable by owning user\n"
952             "    u+w     make file writable by owning user\n"
953             "    u+x     make file executable by owning user\n"
954             "    u-r     make file not readable by owning user\n"
955             "    [also u-w, u-x]\n"
956             "    g+r     make file readable by members of owning group\n"
957             "    [also g+w, g+x, g-r, g-w, g-x]\n"
958             "    o+r     make file readable by all other users\n"
959             "    [also o+w, o+x, o-r, o-w, o-x]\n"
960             "    a+r     make file readable by absolutely everybody\n"
961             "    [also a+w, a+x, a-r, a-w, a-x]\n"
962             "    u+s     enable the Unix set-user-ID bit\n"
963             "    u-s     disable the Unix set-user-ID bit\n"
964             "    g+s     enable the Unix set-group-ID bit\n"
965             "    g-s     disable the Unix set-group-ID bit\n"
966             "    +t      enable the Unix \"sticky bit\"\n"
967             "  You can give more than one modifier for the same user (\"g-rwx\"), and\n"
968             "  more than one user for the same modifier (\"ug+w\"). You can\n"
969             "  use commas to separate different modifiers (\"u+rwx,g+s\").\n",
970             sftp_cmd_chmod
971     },
972     {
973         "del", "delete a file",
974             " <filename>\n"
975             "  Delete a file.\n",
976             sftp_cmd_rm
977     },
978     {
979         "delete", "delete a file",
980             "\n"
981             "  Delete a file.\n",
982             sftp_cmd_rm
983     },
984     {
985         "dir", "list contents of a remote directory",
986             " [ <directory-name> ]\n"
987             "  List the contents of a specified directory on the server.\n"
988             "  If <directory-name> is not given, the current working directory\n"
989             "  will be listed.\n",
990             sftp_cmd_ls
991     },
992     {
993         "exit", "bye", NULL, sftp_cmd_quit
994     },
995     {
996         "get", "download a file from the server to your local machine",
997             " <filename> [ <local-filename> ]\n"
998             "  Downloads a file on the server and stores it locally under\n"
999             "  the same name, or under a different one if you supply the\n"
1000             "  argument <local-filename>.\n",
1001             sftp_cmd_get
1002     },
1003     {
1004         "help", "give help",
1005             " [ <command> [ <command> ... ] ]\n"
1006             "  Give general help if no commands are specified.\n"
1007             "  If one or more commands are specified, give specific help on\n"
1008             "  those particular commands.\n",
1009             sftp_cmd_help
1010     },
1011     {
1012         "ls", "dir", NULL,
1013             sftp_cmd_ls
1014     },
1015     {
1016         "mkdir", "create a directory on the remote server",
1017             " <directory-name>\n"
1018             "  Creates a directory with the given name on the server.\n",
1019             sftp_cmd_mkdir
1020     },
1021     {
1022         "mv", "move or rename a file on the remote server",
1023             " <source-filename> <destination-filename>\n"
1024             "  Moves or renames the file <source-filename> on the server,\n"
1025             "  so that it is accessible under the name <destination-filename>.\n",
1026             sftp_cmd_mv
1027     },
1028     {
1029         "put", "upload a file from your local machine to the server",
1030             " <filename> [ <remote-filename> ]\n"
1031             "  Uploads a file to the server and stores it there under\n"
1032             "  the same name, or under a different one if you supply the\n"
1033             "  argument <remote-filename>.\n",
1034             sftp_cmd_put
1035     },
1036     {
1037         "open", "connect to a host",
1038             " [<user>@]<hostname>\n"
1039             "  Establishes an SFTP connection to a given host. Only usable\n"
1040             "  when you did not already specify a host name on the command\n"
1041             "  line.\n",
1042             sftp_cmd_open
1043     },
1044     {
1045         "pwd", "print your remote working directory",
1046             "\n"
1047             "  Print the current remote working directory for your SFTP session.\n",
1048             sftp_cmd_pwd
1049     },
1050     {
1051         "quit", "bye", NULL,
1052             sftp_cmd_quit
1053     },
1054     {
1055         "reget", "continue downloading a file",
1056             " <filename> [ <local-filename> ]\n"
1057             "  Works exactly like the \"get\" command, but the local file\n"
1058             "  must already exist. The download will begin at the end of the\n"
1059             "  file. This is for resuming a download that was interrupted.\n",
1060             sftp_cmd_reget
1061     },
1062     {
1063         "ren", "mv", NULL,
1064             sftp_cmd_mv
1065     },
1066     {
1067         "rename", "mv", NULL,
1068             sftp_cmd_mv
1069     },
1070     {
1071         "reput", "continue uploading a file",
1072             " <filename> [ <remote-filename> ]\n"
1073             "  Works exactly like the \"put\" command, but the remote file\n"
1074             "  must already exist. The upload will begin at the end of the\n"
1075             "  file. This is for resuming an upload that was interrupted.\n",
1076             sftp_cmd_reput
1077     },
1078     {
1079         "rm", "del", NULL,
1080             sftp_cmd_rm
1081     },
1082     {
1083         "rmdir", "remove a directory on the remote server",
1084             " <directory-name>\n"
1085             "  Removes the directory with the given name on the server.\n"
1086             "  The directory will not be removed unless it is empty.\n",
1087             sftp_cmd_rmdir
1088     }
1089 };
1090
1091 const struct sftp_cmd_lookup *lookup_command(char *name)
1092 {
1093     int i, j, k, cmp;
1094
1095     i = -1;
1096     j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
1097     while (j - i > 1) {
1098         k = (j + i) / 2;
1099         cmp = strcmp(name, sftp_lookup[k].name);
1100         if (cmp < 0)
1101             j = k;
1102         else if (cmp > 0)
1103             i = k;
1104         else {
1105             return &sftp_lookup[k];
1106         }
1107     }
1108     return NULL;
1109 }
1110
1111 static int sftp_cmd_help(struct sftp_command *cmd)
1112 {
1113     int i;
1114     if (cmd->nwords == 1) {
1115         /*
1116          * Give short help on each command.
1117          */
1118         int maxlen;
1119         maxlen = 0;
1120         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1121             int len = strlen(sftp_lookup[i].name);
1122             if (maxlen < len)
1123                 maxlen = len;
1124         }
1125         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
1126             const struct sftp_cmd_lookup *lookup;
1127             lookup = &sftp_lookup[i];
1128             printf("%-*s", maxlen+2, lookup->name);
1129             if (lookup->longhelp == NULL)
1130                 lookup = lookup_command(lookup->shorthelp);
1131             printf("%s\n", lookup->shorthelp);
1132         }
1133     } else {
1134         /*
1135          * Give long help on specific commands.
1136          */
1137         for (i = 1; i < cmd->nwords; i++) {
1138             const struct sftp_cmd_lookup *lookup;
1139             lookup = lookup_command(cmd->words[i]);
1140             if (!lookup) {
1141                 printf("help: %s: command not found\n", cmd->words[i]);
1142             } else {
1143                 printf("%s", lookup->name);
1144                 if (lookup->longhelp == NULL)
1145                     lookup = lookup_command(lookup->shorthelp);
1146                 printf("%s", lookup->longhelp);
1147             }
1148         }
1149     }
1150     return 1;
1151 }
1152
1153 /* ----------------------------------------------------------------------
1154  * Command line reading and parsing.
1155  */
1156 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
1157 {
1158     char *line;
1159     int linelen, linesize;
1160     struct sftp_command *cmd;
1161     char *p, *q, *r;
1162     int quoting;
1163
1164     if ((mode == 0) || (modeflags & 1)) {
1165         printf("psftp> ");
1166     }
1167     fflush(stdout);
1168
1169     cmd = smalloc(sizeof(struct sftp_command));
1170     cmd->words = NULL;
1171     cmd->nwords = 0;
1172     cmd->wordssize = 0;
1173
1174     line = NULL;
1175     linesize = linelen = 0;
1176     while (1) {
1177         int len;
1178         char *ret;
1179
1180         linesize += 512;
1181         line = srealloc(line, linesize);
1182         ret = fgets(line + linelen, linesize - linelen, fp);
1183
1184         if (!ret || (linelen == 0 && line[0] == '\0')) {
1185             cmd->obey = sftp_cmd_quit;
1186             if ((mode == 0) || (modeflags & 1))
1187                 printf("quit\n");
1188             return cmd;                /* eof */
1189         }
1190         len = linelen + strlen(line + linelen);
1191         linelen += len;
1192         if (line[linelen - 1] == '\n') {
1193             linelen--;
1194             line[linelen] = '\0';
1195             break;
1196         }
1197     }
1198     if (modeflags & 1) {
1199         printf("%s\n", line);
1200     }
1201
1202     /*
1203      * Parse the command line into words. The syntax is:
1204      *  - double quotes are removed, but cause spaces within to be
1205      *    treated as non-separating.
1206      *  - a double-doublequote pair is a literal double quote, inside
1207      *    _or_ outside quotes. Like this:
1208      * 
1209      *      firstword "second word" "this has ""quotes"" in" sodoes""this""
1210      * 
1211      * becomes
1212      * 
1213      *      >firstword<
1214      *      >second word<
1215      *      >this has "quotes" in<
1216      *      >sodoes"this"<
1217      */
1218     p = line;
1219     while (*p) {
1220         /* skip whitespace */
1221         while (*p && (*p == ' ' || *p == '\t'))
1222             p++;
1223         /* mark start of word */
1224         q = r = p;                     /* q sits at start, r writes word */
1225         quoting = 0;
1226         while (*p) {
1227             if (!quoting && (*p == ' ' || *p == '\t'))
1228                 break;                 /* reached end of word */
1229             else if (*p == '"' && p[1] == '"')
1230                 p += 2, *r++ = '"';    /* a literal quote */
1231             else if (*p == '"')
1232                 p++, quoting = !quoting;
1233             else
1234                 *r++ = *p++;
1235         }
1236         if (*p)
1237             p++;                       /* skip over the whitespace */
1238         *r = '\0';
1239         if (cmd->nwords >= cmd->wordssize) {
1240             cmd->wordssize = cmd->nwords + 16;
1241             cmd->words =
1242                 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1243         }
1244         cmd->words[cmd->nwords++] = q;
1245     }
1246
1247     /*
1248      * Now parse the first word and assign a function.
1249      */
1250
1251     if (cmd->nwords == 0)
1252         cmd->obey = sftp_cmd_null;
1253     else {
1254         const struct sftp_cmd_lookup *lookup;
1255         lookup = lookup_command(cmd->words[0]);
1256         if (!lookup)
1257             cmd->obey = sftp_cmd_unknown;
1258         else
1259             cmd->obey = lookup->obey;
1260     }
1261
1262     return cmd;
1263 }
1264
1265 static void do_sftp_init(void)
1266 {
1267     /*
1268      * Do protocol initialisation. 
1269      */
1270     if (!fxp_init()) {
1271         fprintf(stderr,
1272                 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1273         return;
1274     }
1275
1276     /*
1277      * Find out where our home directory is.
1278      */
1279     homedir = fxp_realpath(".");
1280     if (!homedir) {
1281         fprintf(stderr,
1282                 "Warning: failed to resolve home directory: %s\n",
1283                 fxp_error());
1284         homedir = dupstr(".");
1285     } else {
1286         printf("Remote working directory is %s\n", homedir);
1287     }
1288     pwd = dupstr(homedir);
1289 }
1290
1291 void do_sftp(int mode, int modeflags, char *batchfile)
1292 {
1293     FILE *fp;
1294     int ret;
1295
1296     /*
1297      * Batch mode?
1298      */
1299     if (mode == 0) {
1300
1301         /* ------------------------------------------------------------------
1302          * Now we're ready to do Real Stuff.
1303          */
1304         while (1) {
1305             struct sftp_command *cmd;
1306             cmd = sftp_getcmd(stdin, 0, 0);
1307             if (!cmd)
1308                 break;
1309             if (cmd->obey(cmd) < 0)
1310                 break;
1311         }
1312     } else {
1313         fp = fopen(batchfile, "r");
1314         if (!fp) {
1315             printf("Fatal: unable to open %s\n", batchfile);
1316             return;
1317         }
1318         while (1) {
1319             struct sftp_command *cmd;
1320             cmd = sftp_getcmd(fp, mode, modeflags);
1321             if (!cmd)
1322                 break;
1323             ret = cmd->obey(cmd);
1324             if (ret < 0)
1325                 break;
1326             if (ret == 0) {
1327                 if (!(modeflags & 2))
1328                     break;
1329             }
1330         }
1331         fclose(fp);
1332
1333     }
1334 }
1335
1336 /* ----------------------------------------------------------------------
1337  * Dirty bits: integration with PuTTY.
1338  */
1339
1340 static int verbose = 0;
1341
1342 void verify_ssh_host_key(char *host, int port, char *keytype,
1343                          char *keystr, char *fingerprint)
1344 {
1345     int ret;
1346     HANDLE hin;
1347     DWORD savemode, i;
1348
1349     static const char absentmsg[] =
1350         "The server's host key is not cached in the registry. You\n"
1351         "have no guarantee that the server is the computer you\n"
1352         "think it is.\n"
1353         "The server's key fingerprint is:\n"
1354         "%s\n"
1355         "If you trust this host, enter \"y\" to add the key to\n"
1356         "PuTTY's cache and carry on connecting.\n"
1357         "If you want to carry on connecting just once, without\n"
1358         "adding the key to the cache, enter \"n\".\n"
1359         "If you do not trust this host, press Return to abandon the\n"
1360         "connection.\n"
1361         "Store key in cache? (y/n) ";
1362
1363     static const char wrongmsg[] =
1364         "WARNING - POTENTIAL SECURITY BREACH!\n"
1365         "The server's host key does not match the one PuTTY has\n"
1366         "cached in the registry. This means that either the\n"
1367         "server administrator has changed the host key, or you\n"
1368         "have actually connected to another computer pretending\n"
1369         "to be the server.\n"
1370         "The new key fingerprint is:\n"
1371         "%s\n"
1372         "If you were expecting this change and trust the new key,\n"
1373         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1374         "If you want to carry on connecting but without updating\n"
1375         "the cache, enter \"n\".\n"
1376         "If you want to abandon the connection completely, press\n"
1377         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1378         "safe choice.\n"
1379         "Update cached key? (y/n, Return cancels connection) ";
1380
1381     static const char abandoned[] = "Connection abandoned.\n";
1382
1383     char line[32];
1384
1385     /*
1386      * Verify the key against the registry.
1387      */
1388     ret = verify_host_key(host, port, keytype, keystr);
1389
1390     if (ret == 0)                      /* success - key matched OK */
1391         return;
1392
1393     if (ret == 2) {                    /* key was different */
1394         fprintf(stderr, wrongmsg, fingerprint);
1395         fflush(stderr);
1396     }
1397     if (ret == 1) {                    /* key was absent */
1398         fprintf(stderr, absentmsg, fingerprint);
1399         fflush(stderr);
1400     }
1401
1402     hin = GetStdHandle(STD_INPUT_HANDLE);
1403     GetConsoleMode(hin, &savemode);
1404     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1405                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1406     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1407     SetConsoleMode(hin, savemode);
1408
1409     if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1410         if (line[0] == 'y' || line[0] == 'Y')
1411             store_host_key(host, port, keytype, keystr);
1412     } else {
1413         fprintf(stderr, abandoned);
1414         exit(0);
1415     }
1416 }
1417
1418 /*
1419  * Ask whether the selected cipher is acceptable (since it was
1420  * below the configured 'warn' threshold).
1421  * cs: 0 = both ways, 1 = client->server, 2 = server->client
1422  */
1423 void askcipher(char *ciphername, int cs)
1424 {
1425     HANDLE hin;
1426     DWORD savemode, i;
1427
1428     static const char msg[] =
1429         "The first %scipher supported by the server is\n"
1430         "%s, which is below the configured warning threshold.\n"
1431         "Continue with connection? (y/n) ";
1432     static const char abandoned[] = "Connection abandoned.\n";
1433
1434     char line[32];
1435
1436     fprintf(stderr, msg,
1437             (cs == 0) ? "" :
1438             (cs == 1) ? "client-to-server " :
1439                         "server-to-client ",
1440             ciphername);
1441     fflush(stderr);
1442
1443     hin = GetStdHandle(STD_INPUT_HANDLE);
1444     GetConsoleMode(hin, &savemode);
1445     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1446                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1447     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1448     SetConsoleMode(hin, savemode);
1449
1450     if (line[0] == 'y' || line[0] == 'Y') {
1451         return;
1452     } else {
1453         fprintf(stderr, abandoned);
1454         exit(0);
1455     }
1456 }
1457
1458 /*
1459  * Warn about the obsolescent key file format.
1460  */
1461 void old_keyfile_warning(void)
1462 {
1463     static const char message[] =
1464         "You are loading an SSH 2 private key which has an\n"
1465         "old version of the file format. This means your key\n"
1466         "file is not fully tamperproof. Future versions of\n"
1467         "PuTTY may stop supporting this private key format,\n"
1468         "so we recommend you convert your key to the new\n"
1469         "format.\n"
1470         "\n"
1471         "Once the key is loaded into PuTTYgen, you can perform\n"
1472         "this conversion simply by saving it again.\n";
1473
1474     fputs(message, stderr);
1475 }
1476
1477 /*
1478  *  Print an error message and perform a fatal exit.
1479  */
1480 void fatalbox(char *fmt, ...)
1481 {
1482     char str[0x100];                   /* Make the size big enough */
1483     va_list ap;
1484     va_start(ap, fmt);
1485     strcpy(str, "Fatal:");
1486     vsprintf(str + strlen(str), fmt, ap);
1487     va_end(ap);
1488     strcat(str, "\n");
1489     fputs(stderr, str);
1490
1491     exit(1);
1492 }
1493 void connection_fatal(char *fmt, ...)
1494 {
1495     char str[0x100];                   /* Make the size big enough */
1496     va_list ap;
1497     va_start(ap, fmt);
1498     strcpy(str, "Fatal:");
1499     vsprintf(str + strlen(str), fmt, ap);
1500     va_end(ap);
1501     strcat(str, "\n");
1502     fputs(stderr, str);
1503
1504     exit(1);
1505 }
1506
1507 void logevent(char *string)
1508 {
1509 }
1510
1511 void ldisc_send(char *buf, int len, int interactive)
1512 {
1513     /*
1514      * This is only here because of the calls to ldisc_send(NULL,
1515      * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1516      * ldisc as an ldisc. So if we get called with any real data, I
1517      * want to know about it.
1518      */
1519     assert(len == 0);
1520 }
1521
1522 /*
1523  * Be told what socket we're supposed to be using.
1524  */
1525 static SOCKET sftp_ssh_socket;
1526 char *do_select(SOCKET skt, int startup)
1527 {
1528     if (startup)
1529         sftp_ssh_socket = skt;
1530     else
1531         sftp_ssh_socket = INVALID_SOCKET;
1532     return NULL;
1533 }
1534 extern int select_result(WPARAM, LPARAM);
1535
1536 /*
1537  * Receive a block of data from the SSH link. Block until all data
1538  * is available.
1539  *
1540  * To do this, we repeatedly call the SSH protocol module, with our
1541  * own trap in from_backend() to catch the data that comes back. We
1542  * do this until we have enough data.
1543  */
1544
1545 static unsigned char *outptr;          /* where to put the data */
1546 static unsigned outlen;                /* how much data required */
1547 static unsigned char *pending = NULL;  /* any spare data */
1548 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */
1549 int from_backend(int is_stderr, char *data, int datalen)
1550 {
1551     unsigned char *p = (unsigned char *) data;
1552     unsigned len = (unsigned) datalen;
1553
1554     /*
1555      * stderr data is just spouted to local stderr and otherwise
1556      * ignored.
1557      */
1558     if (is_stderr) {
1559         fwrite(data, 1, len, stderr);
1560         return 0;
1561     }
1562
1563     /*
1564      * If this is before the real session begins, just return.
1565      */
1566     if (!outptr)
1567         return 0;
1568
1569     if (outlen > 0) {
1570         unsigned used = outlen;
1571         if (used > len)
1572             used = len;
1573         memcpy(outptr, p, used);
1574         outptr += used;
1575         outlen -= used;
1576         p += used;
1577         len -= used;
1578     }
1579
1580     if (len > 0) {
1581         if (pendsize < pendlen + len) {
1582             pendsize = pendlen + len + 4096;
1583             pending = (pending ? srealloc(pending, pendsize) :
1584                        smalloc(pendsize));
1585             if (!pending)
1586                 fatalbox("Out of memory");
1587         }
1588         memcpy(pending + pendlen, p, len);
1589         pendlen += len;
1590     }
1591
1592     return 0;
1593 }
1594 int sftp_recvdata(char *buf, int len)
1595 {
1596     outptr = (unsigned char *) buf;
1597     outlen = len;
1598
1599     /*
1600      * See if the pending-input block contains some of what we
1601      * need.
1602      */
1603     if (pendlen > 0) {
1604         unsigned pendused = pendlen;
1605         if (pendused > outlen)
1606             pendused = outlen;
1607         memcpy(outptr, pending, pendused);
1608         memmove(pending, pending + pendused, pendlen - pendused);
1609         outptr += pendused;
1610         outlen -= pendused;
1611         pendlen -= pendused;
1612         if (pendlen == 0) {
1613             pendsize = 0;
1614             sfree(pending);
1615             pending = NULL;
1616         }
1617         if (outlen == 0)
1618             return 1;
1619     }
1620
1621     while (outlen > 0) {
1622         fd_set readfds;
1623
1624         FD_ZERO(&readfds);
1625         FD_SET(sftp_ssh_socket, &readfds);
1626         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1627             return 0;                  /* doom */
1628         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1629     }
1630
1631     return 1;
1632 }
1633 int sftp_senddata(char *buf, int len)
1634 {
1635     back->send((unsigned char *) buf, len);
1636     return 1;
1637 }
1638
1639 /*
1640  * Loop through the ssh connection and authentication process.
1641  */
1642 static void ssh_sftp_init(void)
1643 {
1644     if (sftp_ssh_socket == INVALID_SOCKET)
1645         return;
1646     while (!back->sendok()) {
1647         fd_set readfds;
1648         FD_ZERO(&readfds);
1649         FD_SET(sftp_ssh_socket, &readfds);
1650         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1651             return;                    /* doom */
1652         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1653     }
1654 }
1655
1656 static char *password = NULL;
1657 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1658 {
1659     HANDLE hin, hout;
1660     DWORD savemode, newmode, i;
1661
1662     if (password) {
1663         static int tried_once = 0;
1664
1665         if (tried_once) {
1666             return 0;
1667         } else {
1668             strncpy(str, password, maxlen);
1669             str[maxlen - 1] = '\0';
1670             tried_once = 1;
1671             return 1;
1672         }
1673     }
1674
1675     hin = GetStdHandle(STD_INPUT_HANDLE);
1676     hout = GetStdHandle(STD_OUTPUT_HANDLE);
1677     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1678         fprintf(stderr, "Cannot get standard input/output handles\n");
1679         exit(1);
1680     }
1681
1682     GetConsoleMode(hin, &savemode);
1683     newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1684     if (is_pw)
1685         newmode &= ~ENABLE_ECHO_INPUT;
1686     else
1687         newmode |= ENABLE_ECHO_INPUT;
1688     SetConsoleMode(hin, newmode);
1689
1690     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1691     ReadFile(hin, str, maxlen - 1, &i, NULL);
1692
1693     SetConsoleMode(hin, savemode);
1694
1695     if ((int) i > maxlen)
1696         i = maxlen - 1;
1697     else
1698         i = i - 2;
1699     str[i] = '\0';
1700
1701     if (is_pw)
1702         WriteFile(hout, "\r\n", 2, &i, NULL);
1703
1704     return 1;
1705 }
1706
1707 /*
1708  *  Initialize the Win$ock driver.
1709  */
1710 static void init_winsock(void)
1711 {
1712     WORD winsock_ver;
1713     WSADATA wsadata;
1714
1715     winsock_ver = MAKEWORD(1, 1);
1716     if (WSAStartup(winsock_ver, &wsadata)) {
1717         fprintf(stderr, "Unable to initialise WinSock");
1718         exit(1);
1719     }
1720     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1721         fprintf(stderr, "WinSock version is incompatible with 1.1");
1722         exit(1);
1723     }
1724 }
1725
1726 /*
1727  *  Short description of parameters.
1728  */
1729 static void usage(void)
1730 {
1731     printf("PuTTY Secure File Transfer (SFTP) client\n");
1732     printf("%s\n", ver);
1733     printf("Usage: psftp [options] user@host\n");
1734     printf("Options:\n");
1735     printf("  -b file   use specified batchfile\n");
1736     printf("  -bc       output batchfile commands\n");
1737     printf("  -be       don't stop batchfile processing if errors\n");
1738     printf("  -v        show verbose messages\n");
1739     printf("  -P port   connect to specified port\n");
1740     printf("  -pw passw login with specified password\n");
1741     exit(1);
1742 }
1743
1744 /*
1745  * Connect to a host.
1746  */
1747 static int psftp_connect(char *userhost, char *user, int portnumber)
1748 {
1749     char *host, *realhost;
1750     char *err;
1751
1752     /* Separate host and username */
1753     host = userhost;
1754     host = strrchr(host, '@');
1755     if (host == NULL) {
1756         host = userhost;
1757     } else {
1758         *host++ = '\0';
1759         if (user) {
1760             printf("psftp: multiple usernames specified; using \"%s\"\n",
1761                    user);
1762         } else
1763             user = userhost;
1764     }
1765
1766     /* Try to load settings for this host */
1767     do_defaults(host, &cfg);
1768     if (cfg.host[0] == '\0') {
1769         /* No settings for this host; use defaults */
1770         do_defaults(NULL, &cfg);
1771         strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1772         cfg.host[sizeof(cfg.host) - 1] = '\0';
1773         cfg.port = 22;
1774     }
1775
1776     /*
1777      * Trim leading whitespace off the hostname if it's there.
1778      */
1779     {
1780         int space = strspn(cfg.host, " \t");
1781         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1782     }
1783
1784     /* See if host is of the form user@host */
1785     if (cfg.host[0] != '\0') {
1786         char *atsign = strchr(cfg.host, '@');
1787         /* Make sure we're not overflowing the user field */
1788         if (atsign) {
1789             if (atsign - cfg.host < sizeof cfg.username) {
1790                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1791                 cfg.username[atsign - cfg.host] = '\0';
1792             }
1793             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1794         }
1795     }
1796
1797     /*
1798      * Trim a colon suffix off the hostname if it's there.
1799      */
1800     cfg.host[strcspn(cfg.host, ":")] = '\0';
1801
1802     /* Set username */
1803     if (user != NULL && user[0] != '\0') {
1804         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1805         cfg.username[sizeof(cfg.username) - 1] = '\0';
1806     }
1807     if (!cfg.username[0]) {
1808         printf("login as: ");
1809         if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1810             fprintf(stderr, "psftp: aborting\n");
1811             exit(1);
1812         } else {
1813             int len = strlen(cfg.username);
1814             if (cfg.username[len - 1] == '\n')
1815                 cfg.username[len - 1] = '\0';
1816         }
1817     }
1818
1819     if (cfg.protocol != PROT_SSH)
1820         cfg.port = 22;
1821
1822     if (portnumber)
1823         cfg.port = portnumber;
1824
1825     /* SFTP uses SSH2 by default always */
1826     cfg.sshprot = 2;
1827
1828     /*
1829      * Disable scary things which shouldn't be enabled for simple
1830      * things like SCP and SFTP: agent forwarding, port forwarding,
1831      * X forwarding.
1832      */
1833     cfg.x11_forward = 0;
1834     cfg.agentfwd = 0;
1835     cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1836
1837     /* Set up subsystem name. */
1838     strcpy(cfg.remote_cmd, "sftp");
1839     cfg.ssh_subsys = TRUE;
1840     cfg.nopty = TRUE;
1841
1842     /*
1843      * Set up fallback option, for SSH1 servers or servers with the
1844      * sftp subsystem not enabled but the server binary installed
1845      * in the usual place. We only support fallback on Unix
1846      * systems, and we use a kludgy piece of shellery which should
1847      * try to find sftp-server in various places (the obvious
1848      * systemwide spots /usr/lib and /usr/local/lib, and then the
1849      * user's PATH) and finally give up.
1850      * 
1851      *   test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1852      *   test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1853      *   exec sftp-server
1854      * 
1855      * the idea being that this will attempt to use either of the
1856      * obvious pathnames and then give up, and when it does give up
1857      * it will print the preferred pathname in the error messages.
1858      */
1859     cfg.remote_cmd_ptr2 =
1860         "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1861         "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1862         "exec sftp-server";
1863     cfg.ssh_subsys2 = FALSE;
1864
1865     back = &ssh_backend;
1866
1867     err = back->init(cfg.host, cfg.port, &realhost, 0);
1868     if (err != NULL) {
1869         fprintf(stderr, "ssh_init: %s\n", err);
1870         return 1;
1871     }
1872     ssh_sftp_init();
1873     if (verbose && realhost != NULL)
1874         printf("Connected to %s\n", realhost);
1875     return 0;
1876 }
1877
1878 /*
1879  * Main program. Parse arguments etc.
1880  */
1881 int main(int argc, char *argv[])
1882 {
1883     int i;
1884     int portnumber = 0;
1885     char *userhost, *user;
1886     int mode = 0;
1887     int modeflags = 0;
1888     char *batchfile = NULL;
1889
1890     flags = FLAG_STDERR | FLAG_INTERACTIVE;
1891     ssh_get_line = &get_line;
1892     init_winsock();
1893     sk_init();
1894
1895     userhost = user = NULL;
1896
1897     for (i = 1; i < argc; i++) {
1898         if (argv[i][0] != '-') {
1899             if (userhost)
1900                 usage();
1901             else
1902                 userhost = dupstr(argv[i]);
1903         } else if (strcmp(argv[i], "-v") == 0) {
1904             verbose = 1, flags |= FLAG_VERBOSE;
1905         } else if (strcmp(argv[i], "-h") == 0 ||
1906                    strcmp(argv[i], "-?") == 0) {
1907             usage();
1908         } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1909             user = argv[++i];
1910         } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1911             portnumber = atoi(argv[++i]);
1912         } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1913             password = argv[++i];
1914         } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1915             mode = 1;
1916             batchfile = argv[++i];
1917         } else if (strcmp(argv[i], "-bc") == 0) {
1918             modeflags = modeflags | 1;
1919         } else if (strcmp(argv[i], "-be") == 0) {
1920             modeflags = modeflags | 2;
1921         } else if (strcmp(argv[i], "--") == 0) {
1922             i++;
1923             break;
1924         } else {
1925             usage();
1926         }
1927     }
1928     argc -= i;
1929     argv += i;
1930     back = NULL;
1931
1932     /*
1933      * If a user@host string has already been provided, connect to
1934      * it now.
1935      */
1936     if (userhost) {
1937         if (psftp_connect(userhost, user, portnumber))
1938             return 1;
1939         do_sftp_init();
1940     } else {
1941         printf("psftp: no hostname specified; use \"open host.name\""
1942             " to connect\n");
1943     }
1944
1945     do_sftp(mode, modeflags, batchfile);
1946
1947     if (back != NULL && back->socket() != NULL) {
1948         char ch;
1949         back->special(TS_EOF);
1950         sftp_recvdata(&ch, 1);
1951     }
1952     WSACleanup();
1953     random_save_seed();
1954
1955     return 0;
1956 }