]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
Retired the #ifdef DUMP_PACKETS stuff in ssh.c because I'm utterly
[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  * Ask whether to wipe a session log file before writing to it.
1460  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
1461  */
1462 int askappend(char *filename)
1463 {
1464     HANDLE hin;
1465     DWORD savemode, i;
1466
1467     static const char msgtemplate[] =
1468         "The session log file \"%.*s\" already exists.\n"
1469         "You can overwrite it with a new session log,\n"
1470         "append your session log to the end of it,\n"
1471         "or disable session logging for this session.\n"
1472         "Enter \"y\" to wipe the file, \"n\" to append to it,\n"
1473         "or just press Return to disable logging.\n"
1474         "Wipe the log file? (y/n, Return cancels logging) ";
1475
1476     char line[32];
1477
1478     fprintf(stderr, msgtemplate, FILENAME_MAX, filename);
1479     fflush(stderr);
1480
1481     hin = GetStdHandle(STD_INPUT_HANDLE);
1482     GetConsoleMode(hin, &savemode);
1483     SetConsoleMode(hin, (savemode | ENABLE_ECHO_INPUT |
1484                          ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT));
1485     ReadFile(hin, line, sizeof(line) - 1, &i, NULL);
1486     SetConsoleMode(hin, savemode);
1487
1488     if (line[0] == 'y' || line[0] == 'Y')
1489         return 2;
1490     else if (line[0] == 'n' || line[0] == 'N')
1491         return 1;
1492     else
1493         return 0;
1494 }
1495
1496 /*
1497  * Warn about the obsolescent key file format.
1498  */
1499 void old_keyfile_warning(void)
1500 {
1501     static const char message[] =
1502         "You are loading an SSH 2 private key which has an\n"
1503         "old version of the file format. This means your key\n"
1504         "file is not fully tamperproof. Future versions of\n"
1505         "PuTTY may stop supporting this private key format,\n"
1506         "so we recommend you convert your key to the new\n"
1507         "format.\n"
1508         "\n"
1509         "Once the key is loaded into PuTTYgen, you can perform\n"
1510         "this conversion simply by saving it again.\n";
1511
1512     fputs(message, stderr);
1513 }
1514
1515 /*
1516  *  Print an error message and perform a fatal exit.
1517  */
1518 void fatalbox(char *fmt, ...)
1519 {
1520     char str[0x100];                   /* Make the size big enough */
1521     va_list ap;
1522     va_start(ap, fmt);
1523     strcpy(str, "Fatal:");
1524     vsprintf(str + strlen(str), fmt, ap);
1525     va_end(ap);
1526     strcat(str, "\n");
1527     fputs(stderr, str);
1528
1529     exit(1);
1530 }
1531 void connection_fatal(char *fmt, ...)
1532 {
1533     char str[0x100];                   /* Make the size big enough */
1534     va_list ap;
1535     va_start(ap, fmt);
1536     strcpy(str, "Fatal:");
1537     vsprintf(str + strlen(str), fmt, ap);
1538     va_end(ap);
1539     strcat(str, "\n");
1540     fputs(stderr, str);
1541
1542     exit(1);
1543 }
1544
1545 void logevent(char *string)
1546 {
1547 }
1548
1549 void ldisc_send(char *buf, int len, int interactive)
1550 {
1551     /*
1552      * This is only here because of the calls to ldisc_send(NULL,
1553      * 0) in ssh.c. Nothing in PSFTP actually needs to use the
1554      * ldisc as an ldisc. So if we get called with any real data, I
1555      * want to know about it.
1556      */
1557     assert(len == 0);
1558 }
1559
1560 /*
1561  * Be told what socket we're supposed to be using.
1562  */
1563 static SOCKET sftp_ssh_socket;
1564 char *do_select(SOCKET skt, int startup)
1565 {
1566     if (startup)
1567         sftp_ssh_socket = skt;
1568     else
1569         sftp_ssh_socket = INVALID_SOCKET;
1570     return NULL;
1571 }
1572 extern int select_result(WPARAM, LPARAM);
1573
1574 /*
1575  * Receive a block of data from the SSH link. Block until all data
1576  * is available.
1577  *
1578  * To do this, we repeatedly call the SSH protocol module, with our
1579  * own trap in from_backend() to catch the data that comes back. We
1580  * do this until we have enough data.
1581  */
1582
1583 static unsigned char *outptr;          /* where to put the data */
1584 static unsigned outlen;                /* how much data required */
1585 static unsigned char *pending = NULL;  /* any spare data */
1586 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */
1587 int from_backend(int is_stderr, char *data, int datalen)
1588 {
1589     unsigned char *p = (unsigned char *) data;
1590     unsigned len = (unsigned) datalen;
1591
1592     /*
1593      * stderr data is just spouted to local stderr and otherwise
1594      * ignored.
1595      */
1596     if (is_stderr) {
1597         fwrite(data, 1, len, stderr);
1598         return 0;
1599     }
1600
1601     /*
1602      * If this is before the real session begins, just return.
1603      */
1604     if (!outptr)
1605         return 0;
1606
1607     if (outlen > 0) {
1608         unsigned used = outlen;
1609         if (used > len)
1610             used = len;
1611         memcpy(outptr, p, used);
1612         outptr += used;
1613         outlen -= used;
1614         p += used;
1615         len -= used;
1616     }
1617
1618     if (len > 0) {
1619         if (pendsize < pendlen + len) {
1620             pendsize = pendlen + len + 4096;
1621             pending = (pending ? srealloc(pending, pendsize) :
1622                        smalloc(pendsize));
1623             if (!pending)
1624                 fatalbox("Out of memory");
1625         }
1626         memcpy(pending + pendlen, p, len);
1627         pendlen += len;
1628     }
1629
1630     return 0;
1631 }
1632 int sftp_recvdata(char *buf, int len)
1633 {
1634     outptr = (unsigned char *) buf;
1635     outlen = len;
1636
1637     /*
1638      * See if the pending-input block contains some of what we
1639      * need.
1640      */
1641     if (pendlen > 0) {
1642         unsigned pendused = pendlen;
1643         if (pendused > outlen)
1644             pendused = outlen;
1645         memcpy(outptr, pending, pendused);
1646         memmove(pending, pending + pendused, pendlen - pendused);
1647         outptr += pendused;
1648         outlen -= pendused;
1649         pendlen -= pendused;
1650         if (pendlen == 0) {
1651             pendsize = 0;
1652             sfree(pending);
1653             pending = NULL;
1654         }
1655         if (outlen == 0)
1656             return 1;
1657     }
1658
1659     while (outlen > 0) {
1660         fd_set readfds;
1661
1662         FD_ZERO(&readfds);
1663         FD_SET(sftp_ssh_socket, &readfds);
1664         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1665             return 0;                  /* doom */
1666         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1667     }
1668
1669     return 1;
1670 }
1671 int sftp_senddata(char *buf, int len)
1672 {
1673     back->send((unsigned char *) buf, len);
1674     return 1;
1675 }
1676
1677 /*
1678  * Loop through the ssh connection and authentication process.
1679  */
1680 static void ssh_sftp_init(void)
1681 {
1682     if (sftp_ssh_socket == INVALID_SOCKET)
1683         return;
1684     while (!back->sendok()) {
1685         fd_set readfds;
1686         FD_ZERO(&readfds);
1687         FD_SET(sftp_ssh_socket, &readfds);
1688         if (select(1, &readfds, NULL, NULL, NULL) < 0)
1689             return;                    /* doom */
1690         select_result((WPARAM) sftp_ssh_socket, (LPARAM) FD_READ);
1691     }
1692 }
1693
1694 static char *password = NULL;
1695 static int get_line(const char *prompt, char *str, int maxlen, int is_pw)
1696 {
1697     HANDLE hin, hout;
1698     DWORD savemode, newmode, i;
1699
1700     if (password) {
1701         static int tried_once = 0;
1702
1703         if (tried_once) {
1704             return 0;
1705         } else {
1706             strncpy(str, password, maxlen);
1707             str[maxlen - 1] = '\0';
1708             tried_once = 1;
1709             return 1;
1710         }
1711     }
1712
1713     hin = GetStdHandle(STD_INPUT_HANDLE);
1714     hout = GetStdHandle(STD_OUTPUT_HANDLE);
1715     if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) {
1716         fprintf(stderr, "Cannot get standard input/output handles\n");
1717         exit(1);
1718     }
1719
1720     GetConsoleMode(hin, &savemode);
1721     newmode = savemode | ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT;
1722     if (is_pw)
1723         newmode &= ~ENABLE_ECHO_INPUT;
1724     else
1725         newmode |= ENABLE_ECHO_INPUT;
1726     SetConsoleMode(hin, newmode);
1727
1728     WriteFile(hout, prompt, strlen(prompt), &i, NULL);
1729     ReadFile(hin, str, maxlen - 1, &i, NULL);
1730
1731     SetConsoleMode(hin, savemode);
1732
1733     if ((int) i > maxlen)
1734         i = maxlen - 1;
1735     else
1736         i = i - 2;
1737     str[i] = '\0';
1738
1739     if (is_pw)
1740         WriteFile(hout, "\r\n", 2, &i, NULL);
1741
1742     return 1;
1743 }
1744
1745 /*
1746  *  Initialize the Win$ock driver.
1747  */
1748 static void init_winsock(void)
1749 {
1750     WORD winsock_ver;
1751     WSADATA wsadata;
1752
1753     winsock_ver = MAKEWORD(1, 1);
1754     if (WSAStartup(winsock_ver, &wsadata)) {
1755         fprintf(stderr, "Unable to initialise WinSock");
1756         exit(1);
1757     }
1758     if (LOBYTE(wsadata.wVersion) != 1 || HIBYTE(wsadata.wVersion) != 1) {
1759         fprintf(stderr, "WinSock version is incompatible with 1.1");
1760         exit(1);
1761     }
1762 }
1763
1764 /*
1765  *  Short description of parameters.
1766  */
1767 static void usage(void)
1768 {
1769     printf("PuTTY Secure File Transfer (SFTP) client\n");
1770     printf("%s\n", ver);
1771     printf("Usage: psftp [options] user@host\n");
1772     printf("Options:\n");
1773     printf("  -b file   use specified batchfile\n");
1774     printf("  -bc       output batchfile commands\n");
1775     printf("  -be       don't stop batchfile processing if errors\n");
1776     printf("  -v        show verbose messages\n");
1777     printf("  -P port   connect to specified port\n");
1778     printf("  -pw passw login with specified password\n");
1779     exit(1);
1780 }
1781
1782 /*
1783  * Connect to a host.
1784  */
1785 static int psftp_connect(char *userhost, char *user, int portnumber)
1786 {
1787     char *host, *realhost;
1788     char *err;
1789
1790     /* Separate host and username */
1791     host = userhost;
1792     host = strrchr(host, '@');
1793     if (host == NULL) {
1794         host = userhost;
1795     } else {
1796         *host++ = '\0';
1797         if (user) {
1798             printf("psftp: multiple usernames specified; using \"%s\"\n",
1799                    user);
1800         } else
1801             user = userhost;
1802     }
1803
1804     /* Try to load settings for this host */
1805     do_defaults(host, &cfg);
1806     if (cfg.host[0] == '\0') {
1807         /* No settings for this host; use defaults */
1808         do_defaults(NULL, &cfg);
1809         strncpy(cfg.host, host, sizeof(cfg.host) - 1);
1810         cfg.host[sizeof(cfg.host) - 1] = '\0';
1811         cfg.port = 22;
1812     }
1813
1814     /*
1815      * Trim leading whitespace off the hostname if it's there.
1816      */
1817     {
1818         int space = strspn(cfg.host, " \t");
1819         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
1820     }
1821
1822     /* See if host is of the form user@host */
1823     if (cfg.host[0] != '\0') {
1824         char *atsign = strchr(cfg.host, '@');
1825         /* Make sure we're not overflowing the user field */
1826         if (atsign) {
1827             if (atsign - cfg.host < sizeof cfg.username) {
1828                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
1829                 cfg.username[atsign - cfg.host] = '\0';
1830             }
1831             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
1832         }
1833     }
1834
1835     /*
1836      * Trim a colon suffix off the hostname if it's there.
1837      */
1838     cfg.host[strcspn(cfg.host, ":")] = '\0';
1839
1840     /* Set username */
1841     if (user != NULL && user[0] != '\0') {
1842         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
1843         cfg.username[sizeof(cfg.username) - 1] = '\0';
1844     }
1845     if (!cfg.username[0]) {
1846         printf("login as: ");
1847         if (!fgets(cfg.username, sizeof(cfg.username), stdin)) {
1848             fprintf(stderr, "psftp: aborting\n");
1849             exit(1);
1850         } else {
1851             int len = strlen(cfg.username);
1852             if (cfg.username[len - 1] == '\n')
1853                 cfg.username[len - 1] = '\0';
1854         }
1855     }
1856
1857     if (cfg.protocol != PROT_SSH)
1858         cfg.port = 22;
1859
1860     if (portnumber)
1861         cfg.port = portnumber;
1862
1863     /* SFTP uses SSH2 by default always */
1864     cfg.sshprot = 2;
1865
1866     /*
1867      * Disable scary things which shouldn't be enabled for simple
1868      * things like SCP and SFTP: agent forwarding, port forwarding,
1869      * X forwarding.
1870      */
1871     cfg.x11_forward = 0;
1872     cfg.agentfwd = 0;
1873     cfg.portfwd[0] = cfg.portfwd[1] = '\0';
1874
1875     /* Set up subsystem name. */
1876     strcpy(cfg.remote_cmd, "sftp");
1877     cfg.ssh_subsys = TRUE;
1878     cfg.nopty = TRUE;
1879
1880     /*
1881      * Set up fallback option, for SSH1 servers or servers with the
1882      * sftp subsystem not enabled but the server binary installed
1883      * in the usual place. We only support fallback on Unix
1884      * systems, and we use a kludgy piece of shellery which should
1885      * try to find sftp-server in various places (the obvious
1886      * systemwide spots /usr/lib and /usr/local/lib, and then the
1887      * user's PATH) and finally give up.
1888      * 
1889      *   test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
1890      *   test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
1891      *   exec sftp-server
1892      * 
1893      * the idea being that this will attempt to use either of the
1894      * obvious pathnames and then give up, and when it does give up
1895      * it will print the preferred pathname in the error messages.
1896      */
1897     cfg.remote_cmd_ptr2 =
1898         "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
1899         "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
1900         "exec sftp-server";
1901     cfg.ssh_subsys2 = FALSE;
1902
1903     back = &ssh_backend;
1904
1905     err = back->init(cfg.host, cfg.port, &realhost, 0);
1906     if (err != NULL) {
1907         fprintf(stderr, "ssh_init: %s\n", err);
1908         return 1;
1909     }
1910     ssh_sftp_init();
1911     if (verbose && realhost != NULL)
1912         printf("Connected to %s\n", realhost);
1913     return 0;
1914 }
1915
1916 /*
1917  * Main program. Parse arguments etc.
1918  */
1919 int main(int argc, char *argv[])
1920 {
1921     int i;
1922     int portnumber = 0;
1923     char *userhost, *user;
1924     int mode = 0;
1925     int modeflags = 0;
1926     char *batchfile = NULL;
1927
1928     flags = FLAG_STDERR | FLAG_INTERACTIVE;
1929     ssh_get_line = &get_line;
1930     init_winsock();
1931     sk_init();
1932
1933     userhost = user = NULL;
1934
1935     for (i = 1; i < argc; i++) {
1936         if (argv[i][0] != '-') {
1937             if (userhost)
1938                 usage();
1939             else
1940                 userhost = dupstr(argv[i]);
1941         } else if (strcmp(argv[i], "-v") == 0) {
1942             verbose = 1, flags |= FLAG_VERBOSE;
1943         } else if (strcmp(argv[i], "-h") == 0 ||
1944                    strcmp(argv[i], "-?") == 0) {
1945             usage();
1946         } else if (strcmp(argv[i], "-l") == 0 && i + 1 < argc) {
1947             user = argv[++i];
1948         } else if (strcmp(argv[i], "-P") == 0 && i + 1 < argc) {
1949             portnumber = atoi(argv[++i]);
1950         } else if (strcmp(argv[i], "-pw") == 0 && i + 1 < argc) {
1951             password = argv[++i];
1952         } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
1953             mode = 1;
1954             batchfile = argv[++i];
1955         } else if (strcmp(argv[i], "-bc") == 0) {
1956             modeflags = modeflags | 1;
1957         } else if (strcmp(argv[i], "-be") == 0) {
1958             modeflags = modeflags | 2;
1959         } else if (strcmp(argv[i], "--") == 0) {
1960             i++;
1961             break;
1962         } else {
1963             usage();
1964         }
1965     }
1966     argc -= i;
1967     argv += i;
1968     back = NULL;
1969
1970     /*
1971      * If a user@host string has already been provided, connect to
1972      * it now.
1973      */
1974     if (userhost) {
1975         if (psftp_connect(userhost, user, portnumber))
1976             return 1;
1977         do_sftp_init();
1978     } else {
1979         printf("psftp: no hostname specified; use \"open host.name\""
1980             " to connect\n");
1981     }
1982
1983     do_sftp(mode, modeflags, batchfile);
1984
1985     if (back != NULL && back->socket() != NULL) {
1986         char ch;
1987         back->special(TS_EOF);
1988         sftp_recvdata(&ch, 1);
1989     }
1990     WSACleanup();
1991     random_save_seed();
1992
1993     return 0;
1994 }