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