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