]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
Tidied up PSFTP batch mode. The gross hack using fxp_error_message
[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             printf("quit\n");
1187             return cmd;                /* eof */
1188         }
1189         len = linelen + strlen(line + linelen);
1190         linelen += len;
1191         if (line[linelen - 1] == '\n') {
1192             linelen--;
1193             line[linelen] = '\0';
1194             break;
1195         }
1196     }
1197     if (modeflags & 1) {
1198         printf("%s\n", line);
1199     }
1200
1201     /*
1202      * Parse the command line into words. The syntax is:
1203      *  - double quotes are removed, but cause spaces within to be
1204      *    treated as non-separating.
1205      *  - a double-doublequote pair is a literal double quote, inside
1206      *    _or_ outside quotes. Like this:
1207      * 
1208      *      firstword "second word" "this has ""quotes"" in" sodoes""this""
1209      * 
1210      * becomes
1211      * 
1212      *      >firstword<
1213      *      >second word<
1214      *      >this has "quotes" in<
1215      *      >sodoes"this"<
1216      */
1217     p = line;
1218     while (*p) {
1219         /* skip whitespace */
1220         while (*p && (*p == ' ' || *p == '\t'))
1221             p++;
1222         /* mark start of word */
1223         q = r = p;                     /* q sits at start, r writes word */
1224         quoting = 0;
1225         while (*p) {
1226             if (!quoting && (*p == ' ' || *p == '\t'))
1227                 break;                 /* reached end of word */
1228             else if (*p == '"' && p[1] == '"')
1229                 p += 2, *r++ = '"';    /* a literal quote */
1230             else if (*p == '"')
1231                 p++, quoting = !quoting;
1232             else
1233                 *r++ = *p++;
1234         }
1235         if (*p)
1236             p++;                       /* skip over the whitespace */
1237         *r = '\0';
1238         if (cmd->nwords >= cmd->wordssize) {
1239             cmd->wordssize = cmd->nwords + 16;
1240             cmd->words =
1241                 srealloc(cmd->words, cmd->wordssize * sizeof(char *));
1242         }
1243         cmd->words[cmd->nwords++] = q;
1244     }
1245
1246     /*
1247      * Now parse the first word and assign a function.
1248      */
1249
1250     if (cmd->nwords == 0)
1251         cmd->obey = sftp_cmd_null;
1252     else {
1253         const struct sftp_cmd_lookup *lookup;
1254         lookup = lookup_command(cmd->words[0]);
1255         if (!lookup)
1256             cmd->obey = sftp_cmd_unknown;
1257         else
1258             cmd->obey = lookup->obey;
1259     }
1260
1261     return cmd;
1262 }
1263
1264 static void do_sftp_init(void)
1265 {
1266     /*
1267      * Do protocol initialisation. 
1268      */
1269     if (!fxp_init()) {
1270         fprintf(stderr,
1271                 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
1272         return;
1273     }
1274
1275     /*
1276      * Find out where our home directory is.
1277      */
1278     homedir = fxp_realpath(".");
1279     if (!homedir) {
1280         fprintf(stderr,
1281                 "Warning: failed to resolve home directory: %s\n",
1282                 fxp_error());
1283         homedir = dupstr(".");
1284     } else {
1285         printf("Remote working directory is %s\n", homedir);
1286     }
1287     pwd = dupstr(homedir);
1288 }
1289
1290 void do_sftp(int mode, int modeflags, char *batchfile)
1291 {
1292     FILE *fp;
1293     int ret;
1294
1295     /*
1296      * Batch mode?
1297      */
1298     if (mode == 0) {
1299
1300         /* ------------------------------------------------------------------
1301          * Now we're ready to do Real Stuff.
1302          */
1303         while (1) {
1304             struct sftp_command *cmd;
1305             cmd = sftp_getcmd(stdin, 0, 0);
1306             if (!cmd)
1307                 break;
1308             if (cmd->obey(cmd) < 0)
1309                 break;
1310         }
1311     } else {
1312         fp = fopen(batchfile, "r");
1313         if (!fp) {
1314             printf("Fatal: unable to open %s\n", batchfile);
1315             return;
1316         }
1317         while (1) {
1318             struct sftp_command *cmd;
1319             cmd = sftp_getcmd(fp, mode, modeflags);
1320             if (!cmd)
1321                 break;
1322             ret = cmd->obey(cmd);
1323             if (ret < 0)
1324                 break;
1325             if (ret == 0) {
1326                 if (!(modeflags & 2))
1327                     break;
1328             }
1329         }
1330         fclose(fp);
1331
1332     }
1333 }
1334
1335 /* ----------------------------------------------------------------------
1336  * Dirty bits: integration with PuTTY.
1337  */
1338
1339 static int verbose = 0;
1340
1341 void verify_ssh_host_key(char *host, int port, char *keytype,
1342                          char *keystr, char *fingerprint)
1343 {
1344     int ret;
1345     HANDLE hin;
1346     DWORD savemode, i;
1347
1348     static const char absentmsg[] =
1349         "The server's host key is not cached in the registry. You\n"
1350         "have no guarantee that the server is the computer you\n"
1351         "think it is.\n"
1352         "The server's key fingerprint is:\n"
1353         "%s\n"
1354         "If you trust this host, enter \"y\" to add the key to\n"
1355         "PuTTY's cache and carry on connecting.\n"
1356         "If you want to carry on connecting just once, without\n"
1357         "adding the key to the cache, enter \"n\".\n"
1358         "If you do not trust this host, press Return to abandon the\n"
1359         "connection.\n"
1360         "Store key in cache? (y/n) ";
1361
1362     static const char wrongmsg[] =
1363         "WARNING - POTENTIAL SECURITY BREACH!\n"
1364         "The server's host key does not match the one PuTTY has\n"
1365         "cached in the registry. This means that either the\n"
1366         "server administrator has changed the host key, or you\n"
1367         "have actually connected to another computer pretending\n"
1368         "to be the server.\n"
1369         "The new key fingerprint is:\n"
1370         "%s\n"
1371         "If you were expecting this change and trust the new key,\n"
1372         "enter \"y\" to update PuTTY's cache and continue connecting.\n"
1373         "If you want to carry on connecting but without updating\n"
1374         "the cache, enter \"n\".\n"
1375         "If you want to abandon the connection completely, press\n"
1376         "Return to cancel. Pressing Return is the ONLY guaranteed\n"
1377         "safe choice.\n"
1378         "Update cached key? (y/n, Return cancels connection) ";
1379
1380     static const char abandoned[] = "Connection abandoned.\n";
1381
1382     char line[32];
1383
1384     /*
1385      * Verify the key against the registry.
1386      */
1387     ret = verify_host_key(host, port, keytype, keystr);
1388
1389     if (ret == 0)                      /* success - key matched OK */
1390         return;
1391
1392     if (ret == 2) {                    /* key was different */
1393         fprintf(stderr, wrongmsg, fingerprint);
1394         fflush(stderr);
1395     }
1396     if (ret == 1) {                    /* key was absent */
1397         fprintf(stderr, absentmsg, fingerprint);
1398         fflush(stderr);
1399     }
1400
1401     hin = GetStdHandle(STD_INPUT_HANDLE);
1402     GetConsoleMode(hin, &savemode);
1403     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1404                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1405     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1406     SetConsoleMode(hin, savemode);
1407
1408     if (line[0] != '\0' && line[0] != '\r' && line[0] != '\n') {
1409         if (line[0] == 'y' || line[0] == 'Y')
1410             store_host_key(host, port, keytype, keystr);
1411     } else {
1412         fprintf(stderr, abandoned);
1413         exit(0);
1414     }
1415 }
1416
1417 /*
1418  * Ask whether the selected cipher is acceptable (since it was
1419  * below the configured 'warn' threshold).
1420  * cs: 0 = both ways, 1 = client->server, 2 = server->client
1421  */
1422 void askcipher(char *ciphername, int cs)
1423 {
1424     HANDLE hin;
1425     DWORD savemode, i;
1426
1427     static const char msg[] =
1428         "The first %scipher supported by the server is\n"
1429         "%s, which is below the configured warning threshold.\n"
1430         "Continue with connection? (y/n) ";
1431     static const char abandoned[] = "Connection abandoned.\n";
1432
1433     char line[32];
1434
1435     fprintf(stderr, msg,
1436             (cs == 0) ? "" :
1437             (cs == 1) ? "client-to-server " :
1438                         "server-to-client ",
1439             ciphername);
1440     fflush(stderr);
1441
1442     hin = GetStdHandle(STD_INPUT_HANDLE);
1443     GetConsoleMode(hin, &savemode);
1444     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1445                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1446     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1447     SetConsoleMode(hin, savemode);
1448
1449     if (line[0] == 'y' || line[0] == 'Y') {
1450         return;
1451     } else {
1452         fprintf(stderr, abandoned);
1453         exit(0);
1454     }
1455 }
1456
1457 /*
1458  * Warn about the obsolescent key file format.
1459  */
1460 void old_keyfile_warning(void)
1461 {
1462     static const char message[] =
1463         "You are loading an SSH 2 private key which has an\n"
1464         "old version of the file format. This means your key\n"
1465         "file is not fully tamperproof. Future versions of\n"
1466         "PuTTY may stop supporting this private key format,\n"
1467         "so we recommend you convert your key to the new\n"
1468         "format.\n"
1469         "\n"
1470         "Once the key is loaded into PuTTYgen, you can perform\n"
1471         "this conversion simply by saving it again.\n";
1472
1473     fputs(message, stderr);
1474 }
1475
1476 /*
1477  *  Print an error message and perform a fatal exit.
1478  */
1479 void fatalbox(char *fmt, ...)
1480 {
1481     char str[0x100];                   /* Make the size big enough */
1482     va_list ap;
1483     va_start(ap, fmt);
1484     strcpy(str, "Fatal:");
1485     vsprintf(str + strlen(str), fmt, ap);
1486     va_end(ap);
1487     strcat(str, "\n");
1488     fputs(stderr, str);
1489
1490     exit(1);
1491 }
1492 void connection_fatal(char *fmt, ...)
1493 {
1494     char str[0x100];                   /* Make the size big enough */
1495     va_list ap;
1496     va_start(ap, fmt);
1497     strcpy(str, "Fatal:");
1498     vsprintf(str + strlen(str), fmt, ap);
1499     va_end(ap);
1500     strcat(str, "\n");
1501     fputs(stderr, str);
1502
1503     exit(1);
1504 }
1505
1506 void logevent(char *string)
1507 {
1508 }
1509
1510 void ldisc_send(char *buf, int len, int interactive)
1511 {
1512     /*
1513      * This is only here because of the calls to ldisc_send(NULL,
1514      * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1515      * ldisc as an ldisc. So if we get called with any real data, I
1516      * want to know about it.
1517      */
1518     assert(len == 0);
1519 }
1520
1521 /*
1522  * Be told what socket we're supposed to be using.
1523  */
1524 static SOCKET sftp_ssh_socket;
1525 char *do_select(SOCKET skt, int startup)
1526 {
1527     if (startup)
1528         sftp_ssh_socket = skt;
1529     else
1530         sftp_ssh_socket = INVALID_SOCKET;
1531     return NULL;
1532 }
1533 extern int select_result(WPARAM, LPARAM);
1534
1535 /*
1536  * Receive a block of data from the SSH link. Block until all data
1537  * is available.
1538  *
1539  * To do this, we repeatedly call the SSH protocol module, with our
1540  * own trap in from_backend() to catch the data that comes back. We
1541  * do this until we have enough data.
1542  */
1543
1544 static unsigned char *outptr;          /* where to put the data */
1545 static unsigned outlen;                /* how much data required */
1546 static unsigned char *pending = NULL;  /* any spare data */
1547 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */
1548 int from_backend(int is_stderr, char *data, int datalen)
1549 {
1550     unsigned char *p = (unsigned char *) data;
1551     unsigned len = (unsigned) datalen;
1552
1553     /*
1554      * stderr data is just spouted to local stderr and otherwise
1555      * ignored.
1556      */
1557     if (is_stderr) {
1558         fwrite(data, 1, len, stderr);
1559         return 0;
1560     }
1561
1562     /*
1563      * If this is before the real session begins, just return.
1564      */
1565     if (!outptr)
1566         return 0;
1567
1568     if (outlen > 0) {
1569         unsigned used = outlen;
1570         if (used > len)
1571             used = len;
1572         memcpy(outptr, p, used);
1573         outptr += used;
1574         outlen -= used;
1575         p += used;
1576         len -= used;
1577     }
1578
1579     if (len > 0) {
1580         if (pendsize < pendlen + len) {
1581             pendsize = pendlen + len + 4096;
1582             pending = (pending ? srealloc(pending, pendsize) :
1583                        smalloc(pendsize));
1584             if (!pending)
1585                 fatalbox("Out of memory");
1586         }
1587         memcpy(pending + pendlen, p, len);
1588         pendlen += len;
1589     }
1590
1591     return 0;
1592 }
1593 int sftp_recvdata(char *buf, int len)
1594 {
1595     outptr = (unsigned char *) buf;
1596     outlen = len;
1597
1598     /*
1599      * See if the pending-input block contains some of what we
1600      * need.
1601      */
1602     if (pendlen > 0) {
1603         unsigned pendused = pendlen;
1604         if (pendused > outlen)
1605             pendused = outlen;
1606         memcpy(outptr, pending, pendused);
1607         memmove(pending, pending + pendused, pendlen - pendused);
1608         outptr += pendused;
1609         outlen -= pendused;
1610         pendlen -= pendused;
1611         if (pendlen == 0) {
1612             pendsize = 0;
1613             sfree(pending);
1614             pending = NULL;
1615         }
1616         if (outlen == 0)
1617             return 1;
1618     }
1619
1620     while (outlen > 0) {
1621         fd_set readfds;
1622
1623         FD_ZERO(&readfds);
1624         FD_SET(sftp_ssh_socket, &readfds);
1625         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1626             return 0;                  /* doom */
1627         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1628     }
1629
1630     return 1;
1631 }
1632 int sftp_senddata(char *buf, int len)
1633 {
1634     back->send((unsigned char *) buf, len);
1635     return 1;
1636 }
1637
1638 /*
1639  * Loop through the ssh connection and authentication process.
1640  */
1641 static void ssh_sftp_init(void)
1642 {
1643     if (sftp_ssh_socket == INVALID_SOCKET)
1644         return;
1645     while (!back->sendok()) {
1646         fd_set readfds;
1647         FD_ZERO(&readfds);
1648         FD_SET(sftp_ssh_socket, &readfds);
1649         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1650             return;                    /* doom */
1651         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1652     }
1653 }
1654
1655 static char *password = NULL;
1656 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1657 {
1658     HANDLE hin, hout;
1659     DWORD savemode, newmode, i;
1660
1661     if (password) {
1662         static int tried_once = 0;
1663
1664         if (tried_once) {
1665             return 0;
1666         } else {
1667             strncpy(str, password, maxlen);
1668             str[maxlen - 1] = '\0';
1669             tried_once = 1;
1670             return 1;
1671         }
1672     }
1673
1674     hin = GetStdHandle(STD_INPUT_HANDLE);
1675     hout = GetStdHandle(STD_OUTPUT_HANDLE);
1676     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1677         fprintf(stderr, "Cannot get standard input/output handles\n");
1678         exit(1);
1679     }
1680
1681     GetConsoleMode(hin, &savemode);
1682     newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1683     if (is_pw)
1684         newmode &= ~ENABLE_ECHO_INPUT;
1685     else
1686         newmode |= ENABLE_ECHO_INPUT;
1687     SetConsoleMode(hin, newmode);
1688
1689     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1690     ReadFile(hin, str, maxlen - 1, &i, NULL);
1691
1692     SetConsoleMode(hin, savemode);
1693
1694     if ((int) i > maxlen)
1695         i = maxlen - 1;
1696     else
1697         i = i - 2;
1698     str[i] = '\0';
1699
1700     if (is_pw)
1701         WriteFile(hout, "\r\n", 2, &i, NULL);
1702
1703     return 1;
1704 }
1705
1706 /*
1707  *  Initialize the Win$ock driver.
1708  */
1709 static void init_winsock(void)
1710 {
1711     WORD winsock_ver;
1712     WSADATA wsadata;
1713
1714     winsock_ver = MAKEWORD(1, 1);
1715     if (WSAStartup(winsock_ver, &wsadata)) {
1716         fprintf(stderr, "Unable to initialise WinSock");
1717         exit(1);
1718     }
1719     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1720         fprintf(stderr, "WinSock version is incompatible with 1.1");
1721         exit(1);
1722     }
1723 }
1724
1725 /*
1726  *  Short description of parameters.
1727  */
1728 static void usage(void)
1729 {
1730     printf("PuTTY Secure File Transfer (SFTP) client\n");
1731     printf("%s\n", ver);
1732     printf("Usage: psftp [options] user@host\n");
1733     printf("Options:\n");
1734     printf("  -b file   use specified batchfile\n");
1735     printf("  -bc       output batchfile commands\n");
1736     printf("  -be       don't stop batchfile processing if errors\n");
1737     printf("  -v        show verbose messages\n");
1738     printf("  -P port   connect to specified port\n");
1739     printf("  -pw passw login with specified password\n");
1740     exit(1);
1741 }
1742
1743 /*
1744  * Connect to a host.
1745  */
1746 static int psftp_connect(char *userhost, char *user, int portnumber)
1747 {
1748     char *host, *realhost;
1749     char *err;
1750
1751     /* Separate host and username */
1752     host = userhost;
1753     host = strrchr(host, '@');
1754     if (host == NULL) {
1755         host = userhost;
1756     } else {
1757         *host++ = '\0';
1758         if (user) {
1759             printf("psftp: multiple usernames specified; using \"%s\"\n",
1760                    user);
1761         } else
1762             user = userhost;
1763     }
1764
1765     /* Try to load settings for this host */
1766     do_defaults(host, &cfg);
1767     if (cfg.host[0] == '\0') {
1768         /* No settings for this host; use defaults */
1769         do_defaults(NULL, &cfg);
1770         strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1771         cfg.host[sizeof(cfg.host) - 1] = '\0';
1772         cfg.port = 22;
1773     }
1774
1775     /*
1776      * Trim leading whitespace off the hostname if it's there.
1777      */
1778     {
1779         int space = strspn(cfg.host, " \t");
1780         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1781     }
1782
1783     /* See if host is of the form user@host */
1784     if (cfg.host[0] != '\0') {
1785         char *atsign = strchr(cfg.host, '@');
1786         /* Make sure we're not overflowing the user field */
1787         if (atsign) {
1788             if (atsign - cfg.host < sizeof cfg.username) {
1789                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1790                 cfg.username[atsign - cfg.host] = '\0';
1791             }
1792             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1793         }
1794     }
1795
1796     /*
1797      * Trim a colon suffix off the hostname if it's there.
1798      */
1799     cfg.host[strcspn(cfg.host, ":")] = '\0';
1800
1801     /* Set username */
1802     if (user != NULL && user[0] != '\0') {
1803         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1804         cfg.username[sizeof(cfg.username) - 1] = '\0';
1805     }
1806     if (!cfg.username[0]) {
1807         printf("login as: ");
1808         if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1809             fprintf(stderr, "psftp: aborting\n");
1810             exit(1);
1811         } else {
1812             int len = strlen(cfg.username);
1813             if (cfg.username[len - 1] == '\n')
1814                 cfg.username[len - 1] = '\0';
1815         }
1816     }
1817
1818     if (cfg.protocol != PROT_SSH)
1819         cfg.port = 22;
1820
1821     if (portnumber)
1822         cfg.port = portnumber;
1823
1824     /* SFTP uses SSH2 by default always */
1825     cfg.sshprot = 2;
1826
1827     /*
1828      * Disable scary things which shouldn't be enabled for simple
1829      * things like SCP and SFTP: agent forwarding, port forwarding,
1830      * X forwarding.
1831      */
1832     cfg.x11_forward = 0;
1833     cfg.agentfwd = 0;
1834     cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1835
1836     /* Set up subsystem name. */
1837     strcpy(cfg.remote_cmd, "sftp");
1838     cfg.ssh_subsys = TRUE;
1839     cfg.nopty = TRUE;
1840
1841     /*
1842      * Set up fallback option, for SSH1 servers or servers with the
1843      * sftp subsystem not enabled but the server binary installed
1844      * in the usual place. We only support fallback on Unix
1845      * systems, and we use a kludgy piece of shellery which should
1846      * try to find sftp-server in various places (the obvious
1847      * systemwide spots /usr/lib and /usr/local/lib, and then the
1848      * user's PATH) and finally give up.
1849      * 
1850      *   test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1851      *   test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1852      *   exec sftp-server
1853      * 
1854      * the idea being that this will attempt to use either of the
1855      * obvious pathnames and then give up, and when it does give up
1856      * it will print the preferred pathname in the error messages.
1857      */
1858     cfg.remote_cmd_ptr2 =
1859         "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1860         "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1861         "exec sftp-server";
1862     cfg.ssh_subsys2 = FALSE;
1863
1864     back = &ssh_backend;
1865
1866     err = back->init(cfg.host, cfg.port, &realhost, 0);
1867     if (err != NULL) {
1868         fprintf(stderr, "ssh_init: %s\n", err);
1869         return 1;
1870     }
1871     ssh_sftp_init();
1872     if (verbose && realhost != NULL)
1873         printf("Connected to %s\n", realhost);
1874     return 0;
1875 }
1876
1877 /*
1878  * Main program. Parse arguments etc.
1879  */
1880 int main(int argc, char *argv[])
1881 {
1882     int i;
1883     int portnumber = 0;
1884     char *userhost, *user;
1885     int mode = 0;
1886     int modeflags = 0;
1887     char *batchfile = NULL;
1888
1889     flags = FLAG_STDERR | FLAG_INTERACTIVE;
1890     ssh_get_line = &get_line;
1891     init_winsock();
1892     sk_init();
1893
1894     userhost = user = NULL;
1895
1896     for (i = 1; i < argc; i++) {
1897         if (argv[i][0] != '-') {
1898             if (userhost)
1899                 usage();
1900             else
1901                 userhost = dupstr(argv[i]);
1902         } else if (strcmp(argv[i], "-v") == 0) {
1903             verbose = 1, flags |= FLAG_VERBOSE;
1904         } else if (strcmp(argv[i], "-h") == 0 ||
1905                    strcmp(argv[i], "-?") == 0) {
1906             usage();
1907         } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1908             user = argv[++i];
1909         } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1910             portnumber = atoi(argv[++i]);
1911         } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1912             password = argv[++i];
1913         } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1914             mode = 1;
1915             batchfile = argv[++i];
1916         } else if (strcmp(argv[i], "-bc") == 0 && i + 1 < argc) {
1917             modeflags = modeflags | 1;
1918         } else if (strcmp(argv[i], "-be") == 0 && i + 1 < argc) {
1919             modeflags = modeflags | 2;
1920         } else if (strcmp(argv[i], "--") == 0) {
1921             i++;
1922             break;
1923         } else {
1924             usage();
1925         }
1926     }
1927     argc -= i;
1928     argv += i;
1929     back = NULL;
1930
1931     /*
1932      * If a user@host string has already been provided, connect to
1933      * it now.
1934      */
1935     if (userhost) {
1936         if (psftp_connect(userhost, user, portnumber))
1937             return 1;
1938         do_sftp_init();
1939     } else {
1940         printf("psftp: no hostname specified; use \"open host.name\""
1941             " to connect\n");
1942     }
1943
1944     do_sftp(mode, modeflags, batchfile);
1945
1946     if (back != NULL && back->socket() != NULL) {
1947         char ch;
1948         back->special(TS_EOF);
1949         sftp_recvdata(&ch, 1);
1950     }
1951     WSACleanup();
1952     random_save_seed();
1953
1954     return 0;
1955 }