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