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