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