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