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