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