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