]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - psftp.c
In the file-transfer applications, which only ever use the main
[PuTTY.git] / psftp.c
1 /*
2  * psftp.c: (platform-independent) front end for PSFTP.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <stdarg.h>
8 #include <assert.h>
9 #include <limits.h>
10
11 #define PUTTY_DO_GLOBALS
12 #include "putty.h"
13 #include "psftp.h"
14 #include "storage.h"
15 #include "ssh.h"
16 #include "sftp.h"
17 #include "int64.h"
18
19 /*
20  * Since SFTP is a request-response oriented protocol, it requires
21  * no buffer management: when we send data, we stop and wait for an
22  * acknowledgement _anyway_, and so we can't possibly overfill our
23  * send buffer.
24  */
25
26 static int psftp_connect(char *userhost, char *user, int portnumber);
27 static int do_sftp_init(void);
28 void do_sftp_cleanup();
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     struct sftp_packet *pktin;
52     struct sftp_request *req, *rreq;
53
54     if (name[0] == '/') {
55         fullname = dupstr(name);
56     } else {
57         char *slash;
58         if (pwd[strlen(pwd) - 1] == '/')
59             slash = "";
60         else
61             slash = "/";
62         fullname = dupcat(pwd, slash, name, NULL);
63     }
64
65     sftp_register(req = fxp_realpath_send(fullname));
66     rreq = sftp_find_request(pktin = sftp_recv());
67     assert(rreq == req);
68     canonname = fxp_realpath_recv(pktin, rreq);
69
70     if (canonname) {
71         sfree(fullname);
72         return canonname;
73     } else {
74         /*
75          * Attempt number 2. Some FXP_REALPATH implementations
76          * (glibc-based ones, in particular) require the _whole_
77          * path to point to something that exists, whereas others
78          * (BSD-based) only require all but the last component to
79          * exist. So if the first call failed, we should strip off
80          * everything from the last slash onwards and try again,
81          * then put the final component back on.
82          * 
83          * Special cases:
84          * 
85          *  - if the last component is "/." or "/..", then we don't
86          *    bother trying this because there's no way it can work.
87          * 
88          *  - if the thing actually ends with a "/", we remove it
89          *    before we start. Except if the string is "/" itself
90          *    (although I can't see why we'd have got here if so,
91          *    because surely "/" would have worked the first
92          *    time?), in which case we don't bother.
93          * 
94          *  - if there's no slash in the string at all, give up in
95          *    confusion (we expect at least one because of the way
96          *    we constructed the string).
97          */
98
99         int i;
100         char *returnname;
101
102         i = strlen(fullname);
103         if (i > 2 && fullname[i - 1] == '/')
104             fullname[--i] = '\0';      /* strip trailing / unless at pos 0 */
105         while (i > 0 && fullname[--i] != '/');
106
107         /*
108          * Give up on special cases.
109          */
110         if (fullname[i] != '/' ||      /* no slash at all */
111             !strcmp(fullname + i, "/.") ||      /* ends in /. */
112             !strcmp(fullname + i, "/..") ||     /* ends in /.. */
113             !strcmp(fullname, "/")) {
114             return fullname;
115         }
116
117         /*
118          * Now i points at the slash. Deal with the final special
119          * case i==0 (ie the whole path was "/nonexistentfile").
120          */
121         fullname[i] = '\0';            /* separate the string */
122         if (i == 0) {
123             sftp_register(req = fxp_realpath_send("/"));
124         } else {
125             sftp_register(req = fxp_realpath_send(fullname));
126         }
127         rreq = sftp_find_request(pktin = sftp_recv());
128         assert(rreq == req);
129         canonname = fxp_realpath_recv(pktin, rreq);
130
131         if (!canonname) {
132             /* Even that failed. Restore our best guess at the
133              * constructed filename and give up */
134             fullname[i] = '/';  /* restore slash and last component */
135             return fullname;
136         }
137
138         /*
139          * We have a canonical name for all but the last path
140          * component. Concatenate the last component and return.
141          */
142         returnname = dupcat(canonname,
143                             canonname[strlen(canonname) - 1] ==
144                             '/' ? "" : "/", fullname + i + 1, NULL);
145         sfree(fullname);
146         sfree(canonname);
147         return returnname;
148     }
149 }
150
151 /*
152  * Return a pointer to the portion of str that comes after the last
153  * slash (or backslash or colon, if `local' is TRUE).
154  */
155 static char *stripslashes(char *str, int local)
156 {
157     char *p;
158
159     if (local) {
160         p = strchr(str, ':');
161         if (p) str = p+1;
162     }
163
164     p = strrchr(str, '/');
165     if (p) str = p+1;
166
167     if (local) {
168         p = strrchr(str, '\\');
169         if (p) str = p+1;
170     }
171
172     return str;
173 }
174
175 /*
176  * qsort comparison routine for fxp_name structures. Sorts by real
177  * file name.
178  */
179 static int sftp_name_compare(const void *av, const void *bv)
180 {
181     const struct fxp_name *const *a = (const struct fxp_name *const *) av;
182     const struct fxp_name *const *b = (const struct fxp_name *const *) bv;
183     return strcmp((*a)->filename, (*b)->filename);
184 }
185
186 /*
187  * Likewise, but for a bare char *.
188  */
189 static int bare_name_compare(const void *av, const void *bv)
190 {
191     const char **a = (const char **) av;
192     const char **b = (const char **) bv;
193     return strcmp(*a, *b);
194 }
195
196 static void not_connected(void)
197 {
198     printf("psftp: not connected to a host; use \"open host.name\"\n");
199 }
200
201 /* ----------------------------------------------------------------------
202  * The meat of the `get' and `put' commands.
203  */
204 int sftp_get_file(char *fname, char *outfname, int recurse, int restart)
205 {
206     struct fxp_handle *fh;
207     struct sftp_packet *pktin;
208     struct sftp_request *req, *rreq;
209     struct fxp_xfer *xfer;
210     uint64 offset;
211     WFile *file;
212     int ret, shown_err = FALSE;
213
214     /*
215      * In recursive mode, see if we're dealing with a directory.
216      * (If we're not in recursive mode, we need not even check: the
217      * subsequent FXP_OPEN will return a usable error message.)
218      */
219     if (recurse) {
220         struct fxp_attrs attrs;
221         int result;
222
223         sftp_register(req = fxp_stat_send(fname));
224         rreq = sftp_find_request(pktin = sftp_recv());
225         assert(rreq == req);
226         result = fxp_stat_recv(pktin, rreq, &attrs);
227
228         if (result &&
229             (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
230             (attrs.permissions & 0040000)) {
231
232             struct fxp_handle *dirhandle;
233             int nnames, namesize;
234             struct fxp_name **ournames;
235             struct fxp_names *names;
236             int i;
237
238             /*
239              * First, attempt to create the destination directory,
240              * unless it already exists.
241              */
242             if (file_type(outfname) != FILE_TYPE_DIRECTORY &&
243                 !create_directory(outfname)) {
244                 printf("%s: Cannot create directory\n", outfname);
245                 return 0;
246             }
247
248             /*
249              * Now get the list of filenames in the remote
250              * directory.
251              */
252             sftp_register(req = fxp_opendir_send(fname));
253             rreq = sftp_find_request(pktin = sftp_recv());
254             assert(rreq == req);
255             dirhandle = fxp_opendir_recv(pktin, rreq);
256
257             if (!dirhandle) {
258                 printf("%s: unable to open directory: %s\n",
259                        fname, fxp_error());
260                 return 0;
261             }
262             nnames = namesize = 0;
263             ournames = NULL;
264             while (1) {
265                 int i;
266
267                 sftp_register(req = fxp_readdir_send(dirhandle));
268                 rreq = sftp_find_request(pktin = sftp_recv());
269                 assert(rreq == req);
270                 names = fxp_readdir_recv(pktin, rreq);
271
272                 if (names == NULL) {
273                     if (fxp_error_type() == SSH_FX_EOF)
274                         break;
275                     printf("%s: reading directory: %s\n", fname, fxp_error());
276                     sfree(ournames);
277                     return 0;
278                 }
279                 if (names->nnames == 0) {
280                     fxp_free_names(names);
281                     break;
282                 }
283                 if (nnames + names->nnames >= namesize) {
284                     namesize += names->nnames + 128;
285                     ournames = sresize(ournames, namesize, struct fxp_name *);
286                 }
287                 for (i = 0; i < names->nnames; i++)
288                     if (strcmp(names->names[i].filename, ".") &&
289                         strcmp(names->names[i].filename, "..")) {
290                         if (!vet_filename(names->names[i].filename)) {
291                             printf("ignoring potentially dangerous server-"
292                                    "supplied filename '%s'\n",
293                                    names->names[i].filename);
294                         } else {
295                             ournames[nnames++] =
296                                 fxp_dup_name(&names->names[i]);
297                         }
298                     }
299                 fxp_free_names(names);
300             }
301             sftp_register(req = fxp_close_send(dirhandle));
302             rreq = sftp_find_request(pktin = sftp_recv());
303             assert(rreq == req);
304             fxp_close_recv(pktin, rreq);
305
306             /*
307              * Sort the names into a clear order. This ought to
308              * make things more predictable when we're doing a
309              * reget of the same directory, just in case two
310              * readdirs on the same remote directory return a
311              * different order.
312              */
313             qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);
314
315             /*
316              * If we're in restart mode, find the last filename on
317              * this list that already exists. We may have to do a
318              * reget on _that_ file, but shouldn't have to do
319              * anything on the previous files.
320              * 
321              * If none of them exists, of course, we start at 0.
322              */
323             i = 0;
324             if (restart) {
325                 while (i < nnames) {
326                     char *nextoutfname;
327                     int ret;
328                     if (outfname)
329                         nextoutfname = dir_file_cat(outfname,
330                                                     ournames[i]->filename);
331                     else
332                         nextoutfname = dupstr(ournames[i]->filename);
333                     ret = (file_type(nextoutfname) == FILE_TYPE_NONEXISTENT);
334                     sfree(nextoutfname);
335                     if (ret)
336                         break;
337                     i++;
338                 }
339                 if (i > 0)
340                     i--;
341             }
342
343             /*
344              * Now we're ready to recurse. Starting at ournames[i]
345              * and continuing on to the end of the list, we
346              * construct a new source and target file name, and
347              * call sftp_get_file again.
348              */
349             for (; i < nnames; i++) {
350                 char *nextfname, *nextoutfname;
351                 int ret;
352                 
353                 nextfname = dupcat(fname, "/", ournames[i]->filename, NULL);
354                 if (outfname)
355                     nextoutfname = dir_file_cat(outfname,
356                                                 ournames[i]->filename);
357                 else
358                     nextoutfname = dupstr(ournames[i]->filename);
359                 ret = sftp_get_file(nextfname, nextoutfname, recurse, restart);
360                 restart = FALSE;       /* after first partial file, do full */
361                 sfree(nextoutfname);
362                 sfree(nextfname);
363                 if (!ret) {
364                     for (i = 0; i < nnames; i++) {
365                         fxp_free_name(ournames[i]);
366                     }
367                     sfree(ournames);
368                     return 0;
369                 }
370             }
371
372             /*
373              * Done this recursion level. Free everything.
374              */
375             for (i = 0; i < nnames; i++) {
376                 fxp_free_name(ournames[i]);
377             }
378             sfree(ournames);
379
380             return 1;
381         }
382     }
383
384     sftp_register(req = fxp_open_send(fname, SSH_FXF_READ));
385     rreq = sftp_find_request(pktin = sftp_recv());
386     assert(rreq == req);
387     fh = fxp_open_recv(pktin, rreq);
388
389     if (!fh) {
390         printf("%s: open for read: %s\n", fname, fxp_error());
391         return 0;
392     }
393
394     if (restart) {
395         file = open_existing_wfile(outfname, NULL);
396     } else {
397         file = open_new_file(outfname);
398     }
399
400     if (!file) {
401         printf("local: unable to open %s\n", outfname);
402
403         sftp_register(req = fxp_close_send(fh));
404         rreq = sftp_find_request(pktin = sftp_recv());
405         assert(rreq == req);
406         fxp_close_recv(pktin, rreq);
407
408         return 0;
409     }
410
411     if (restart) {
412         char decbuf[30];
413         if (seek_file(file, uint64_make(0,0) , FROM_END) == -1) {
414             printf("reget: cannot restart %s - file too large\n",
415                    outfname);
416                 sftp_register(req = fxp_close_send(fh));
417                 rreq = sftp_find_request(pktin = sftp_recv());
418                 assert(rreq == req);
419                 fxp_close_recv(pktin, rreq);
420                 
421                 return 0;
422         }
423             
424         offset = get_file_posn(file);
425         uint64_decimal(offset, decbuf);
426         printf("reget: restarting at file position %s\n", decbuf);
427     } else {
428         offset = uint64_make(0, 0);
429     }
430
431     printf("remote:%s => local:%s\n", fname, outfname);
432
433     /*
434      * FIXME: we can use FXP_FSTAT here to get the file size, and
435      * thus put up a progress bar.
436      */
437     ret = 1;
438     xfer = xfer_download_init(fh, offset);
439     while (!xfer_done(xfer)) {
440         void *vbuf;
441         int ret, len;
442         int wpos, wlen;
443
444         xfer_download_queue(xfer);
445         pktin = sftp_recv();
446         ret = xfer_download_gotpkt(xfer, pktin);
447
448         if (ret < 0) {
449             if (!shown_err) {
450                 printf("error while reading: %s\n", fxp_error());
451                 shown_err = TRUE;
452             }
453             ret = 0;
454         }
455
456         while (xfer_download_data(xfer, &vbuf, &len)) {
457             unsigned char *buf = (unsigned char *)vbuf;
458
459             wpos = 0;
460             while (wpos < len) {
461                 wlen = write_to_file(file, buf + wpos, len - wpos);
462                 if (wlen <= 0) {
463                     printf("error while writing local file\n");
464                     ret = 0;
465                     xfer_set_error(xfer);
466                     break;
467                 }
468                 wpos += wlen;
469             }
470             if (wpos < len) {          /* we had an error */
471                 ret = 0;
472                 xfer_set_error(xfer);
473             }
474
475             sfree(vbuf);
476         }
477     }
478
479     xfer_cleanup(xfer);
480
481     close_wfile(file);
482
483     sftp_register(req = fxp_close_send(fh));
484     rreq = sftp_find_request(pktin = sftp_recv());
485     assert(rreq == req);
486     fxp_close_recv(pktin, rreq);
487
488     return ret;
489 }
490
491 int sftp_put_file(char *fname, char *outfname, int recurse, int restart)
492 {
493     struct fxp_handle *fh;
494     struct fxp_xfer *xfer;
495     struct sftp_packet *pktin;
496     struct sftp_request *req, *rreq;
497     uint64 offset;
498     RFile *file;
499     int ret, err, eof;
500
501     /*
502      * In recursive mode, see if we're dealing with a directory.
503      * (If we're not in recursive mode, we need not even check: the
504      * subsequent fopen will return an error message.)
505      */
506     if (recurse && file_type(fname) == FILE_TYPE_DIRECTORY) {
507         struct fxp_attrs attrs;
508         int result;
509         int nnames, namesize;
510         char *name, **ournames;
511         DirHandle *dh;
512         int i;
513
514         /*
515          * First, attempt to create the destination directory,
516          * unless it already exists.
517          */
518         sftp_register(req = fxp_stat_send(outfname));
519         rreq = sftp_find_request(pktin = sftp_recv());
520         assert(rreq == req);
521         result = fxp_stat_recv(pktin, rreq, &attrs);
522         if (!result ||
523             !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
524             !(attrs.permissions & 0040000)) {
525             sftp_register(req = fxp_mkdir_send(outfname));
526             rreq = sftp_find_request(pktin = sftp_recv());
527             assert(rreq == req);
528             result = fxp_mkdir_recv(pktin, rreq);
529
530             if (!result) {
531                 printf("%s: create directory: %s\n",
532                        outfname, fxp_error());
533                 return 0;
534             }
535         }
536
537         /*
538          * Now get the list of filenames in the local directory.
539          */
540         nnames = namesize = 0;
541         ournames = NULL;
542
543         dh = open_directory(fname);
544         if (!dh) {
545             printf("%s: unable to open directory\n", fname);
546             return 0;
547         }
548         while ((name = read_filename(dh)) != NULL) {
549             if (nnames >= namesize) {
550                 namesize += 128;
551                 ournames = sresize(ournames, namesize, char *);
552             }
553             ournames[nnames++] = name;
554         }
555         close_directory(dh);
556
557         /*
558          * Sort the names into a clear order. This ought to make
559          * things more predictable when we're doing a reput of the
560          * same directory, just in case two readdirs on the same
561          * local directory return a different order.
562          */
563         qsort(ournames, nnames, sizeof(*ournames), bare_name_compare);
564
565         /*
566          * If we're in restart mode, find the last filename on this
567          * list that already exists. We may have to do a reput on
568          * _that_ file, but shouldn't have to do anything on the
569          * previous files.
570          *
571          * If none of them exists, of course, we start at 0.
572          */
573         i = 0;
574         if (restart) {
575             while (i < nnames) {
576                 char *nextoutfname;
577                 nextoutfname = dupcat(outfname, "/", ournames[i], NULL);
578                 sftp_register(req = fxp_stat_send(nextoutfname));
579                 rreq = sftp_find_request(pktin = sftp_recv());
580                 assert(rreq == req);
581                 result = fxp_stat_recv(pktin, rreq, &attrs);
582                 sfree(nextoutfname);
583                 if (!result)
584                     break;
585                 i++;
586             }
587             if (i > 0)
588                 i--;
589         }
590
591         /*
592          * Now we're ready to recurse. Starting at ournames[i]
593          * and continuing on to the end of the list, we
594          * construct a new source and target file name, and
595          * call sftp_put_file again.
596          */
597         for (; i < nnames; i++) {
598             char *nextfname, *nextoutfname;
599             int ret;
600
601             if (fname)
602                 nextfname = dir_file_cat(fname, ournames[i]);
603             else
604                 nextfname = dupstr(ournames[i]);
605             nextoutfname = dupcat(outfname, "/", ournames[i], NULL);
606             ret = sftp_put_file(nextfname, nextoutfname, recurse, restart);
607             restart = FALSE;           /* after first partial file, do full */
608             sfree(nextoutfname);
609             sfree(nextfname);
610             if (!ret) {
611                 for (i = 0; i < nnames; i++) {
612                     sfree(ournames[i]);
613                 }
614                 sfree(ournames);
615                 return 0;
616             }
617         }
618
619         /*
620          * Done this recursion level. Free everything.
621          */
622         for (i = 0; i < nnames; i++) {
623             sfree(ournames[i]);
624         }
625         sfree(ournames);
626
627         return 1;
628     }
629
630     file = open_existing_file(fname, NULL, NULL, NULL);
631     if (!file) {
632         printf("local: unable to open %s\n", fname);
633         return 0;
634     }
635     if (restart) {
636         sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE));
637     } else {
638         sftp_register(req = fxp_open_send(outfname, SSH_FXF_WRITE |
639                                           SSH_FXF_CREAT | SSH_FXF_TRUNC));
640     }
641     rreq = sftp_find_request(pktin = sftp_recv());
642     assert(rreq == req);
643     fh = fxp_open_recv(pktin, rreq);
644
645     if (!fh) {
646         printf("%s: open for write: %s\n", outfname, fxp_error());
647         return 0;
648     }
649
650     if (restart) {
651         char decbuf[30];
652         struct fxp_attrs attrs;
653         int ret;
654
655         sftp_register(req = fxp_fstat_send(fh));
656         rreq = sftp_find_request(pktin = sftp_recv());
657         assert(rreq == req);
658         ret = fxp_fstat_recv(pktin, rreq, &attrs);
659
660         if (!ret) {
661             printf("read size of %s: %s\n", outfname, fxp_error());
662             return 0;
663         }
664         if (!(attrs.flags & SSH_FILEXFER_ATTR_SIZE)) {
665             printf("read size of %s: size was not given\n", outfname);
666             return 0;
667         }
668         offset = attrs.size;
669         uint64_decimal(offset, decbuf);
670         printf("reput: restarting at file position %s\n", decbuf);
671
672         if (seek_file((WFile *)file, offset, FROM_START) != 0)
673             seek_file((WFile *)file, uint64_make(0,0), FROM_END);    /* *shrug* */
674     } else {
675         offset = uint64_make(0, 0);
676     }
677
678     printf("local:%s => remote:%s\n", fname, outfname);
679
680     /*
681      * FIXME: we can use FXP_FSTAT here to get the file size, and
682      * thus put up a progress bar.
683      */
684     ret = 1;
685     xfer = xfer_upload_init(fh, offset);
686     err = eof = 0;
687     while ((!err && !eof) || !xfer_done(xfer)) {
688         char buffer[4096];
689         int len, ret;
690
691         while (xfer_upload_ready(xfer) && !err && !eof) {
692             len = read_from_file(file, buffer, sizeof(buffer));
693             if (len == -1) {
694                 printf("error while reading local file\n");
695                 err = 1;
696             } else if (len == 0) {
697                 eof = 1;
698             } else {
699                 xfer_upload_data(xfer, buffer, len);
700             }
701         }
702
703         if (!xfer_done(xfer)) {
704             pktin = sftp_recv();
705             ret = xfer_upload_gotpkt(xfer, pktin);
706             if (!ret) {
707                 printf("error while writing: %s\n", fxp_error());
708                 err = 1;
709             }
710         }
711     }
712
713     xfer_cleanup(xfer);
714
715     sftp_register(req = fxp_close_send(fh));
716     rreq = sftp_find_request(pktin = sftp_recv());
717     assert(rreq == req);
718     fxp_close_recv(pktin, rreq);
719
720     close_rfile(file);
721
722     return ret;
723 }
724
725 /* ----------------------------------------------------------------------
726  * A remote wildcard matcher, providing a similar interface to the
727  * local one in psftp.h.
728  */
729
730 typedef struct SftpWildcardMatcher {
731     struct fxp_handle *dirh;
732     struct fxp_names *names;
733     int namepos;
734     char *wildcard, *prefix;
735 } SftpWildcardMatcher;
736
737 SftpWildcardMatcher *sftp_begin_wildcard_matching(char *name)
738 {
739     struct sftp_packet *pktin;
740     struct sftp_request *req, *rreq;
741     char *wildcard;
742     char *unwcdir, *tmpdir, *cdir;
743     int len, check;
744     SftpWildcardMatcher *swcm;
745     struct fxp_handle *dirh;
746
747     /*
748      * We don't handle multi-level wildcards; so we expect to find
749      * a fully specified directory part, followed by a wildcard
750      * after that.
751      */
752     wildcard = stripslashes(name, 0);
753
754     unwcdir = dupstr(name);
755     len = wildcard - name;
756     unwcdir[len] = '\0';
757     if (len > 0 && unwcdir[len-1] == '/')
758         unwcdir[len-1] = '\0';
759     tmpdir = snewn(1 + len, char);
760     check = wc_unescape(tmpdir, unwcdir);
761     sfree(tmpdir);
762
763     if (!check) {
764         printf("Multiple-level wildcards are not supported\n");
765         sfree(unwcdir);
766         return NULL;
767     }
768
769     cdir = canonify(unwcdir);
770
771     sftp_register(req = fxp_opendir_send(cdir));
772     rreq = sftp_find_request(pktin = sftp_recv());
773     assert(rreq == req);
774     dirh = fxp_opendir_recv(pktin, rreq);
775
776     if (dirh) {
777         swcm = snew(SftpWildcardMatcher);
778         swcm->dirh = dirh;
779         swcm->names = NULL;
780         swcm->wildcard = dupstr(wildcard);
781         swcm->prefix = unwcdir;
782     } else {
783         printf("Unable to open %s: %s\n", cdir, fxp_error());
784         swcm = NULL;
785         sfree(unwcdir);
786     }
787
788     sfree(cdir);
789
790     return swcm;
791 }
792
793 char *sftp_wildcard_get_filename(SftpWildcardMatcher *swcm)
794 {
795     struct fxp_name *name;
796     struct sftp_packet *pktin;
797     struct sftp_request *req, *rreq;
798
799     while (1) {
800         if (swcm->names && swcm->namepos >= swcm->names->nnames) {
801             fxp_free_names(swcm->names);
802             swcm->names = NULL;
803         }
804
805         if (!swcm->names) {
806             sftp_register(req = fxp_readdir_send(swcm->dirh));
807             rreq = sftp_find_request(pktin = sftp_recv());
808             assert(rreq == req);
809             swcm->names = fxp_readdir_recv(pktin, rreq);
810
811             if (!swcm->names) {
812                 if (fxp_error_type() != SSH_FX_EOF)
813                     printf("%s: reading directory: %s\n", swcm->prefix,
814                            fxp_error());
815                 return NULL;
816             }
817
818             swcm->namepos = 0;
819         }
820
821         assert(swcm->names && swcm->namepos < swcm->names->nnames);
822
823         name = &swcm->names->names[swcm->namepos++];
824
825         if (!strcmp(name->filename, ".") || !strcmp(name->filename, ".."))
826             continue;                  /* expected bad filenames */
827
828         if (!vet_filename(name->filename)) {
829             printf("ignoring potentially dangerous server-"
830                    "supplied filename '%s'\n", name->filename);
831             continue;                  /* unexpected bad filename */
832         }
833
834         if (!wc_match(swcm->wildcard, name->filename))
835             continue;                  /* doesn't match the wildcard */
836
837         /*
838          * We have a working filename. Return it.
839          */
840         return dupprintf("%s%s%s", swcm->prefix,
841                          (!swcm->prefix[0] ||
842                           swcm->prefix[strlen(swcm->prefix)-1]=='/' ?
843                           "" : "/"),
844                          name->filename);
845     }
846 }
847
848 void sftp_finish_wildcard_matching(SftpWildcardMatcher *swcm)
849 {
850     struct sftp_packet *pktin;
851     struct sftp_request *req, *rreq;
852
853     sftp_register(req = fxp_close_send(swcm->dirh));
854     rreq = sftp_find_request(pktin = sftp_recv());
855     assert(rreq == req);
856     fxp_close_recv(pktin, rreq);
857
858     if (swcm->names)
859         fxp_free_names(swcm->names);
860
861     sfree(swcm->prefix);
862     sfree(swcm->wildcard);
863
864     sfree(swcm);
865 }
866
867 /*
868  * General function to match a potential wildcard in a filename
869  * argument and iterate over every matching file. Used in several
870  * PSFTP commands (rmdir, rm, chmod, mv).
871  */
872 int wildcard_iterate(char *filename, int (*func)(void *, char *), void *ctx)
873 {
874     char *unwcfname, *newname, *cname;
875     int is_wc, ret;
876
877     unwcfname = snewn(strlen(filename)+1, char);
878     is_wc = !wc_unescape(unwcfname, filename);
879
880     if (is_wc) {
881         SftpWildcardMatcher *swcm = sftp_begin_wildcard_matching(filename);
882         int matched = FALSE;
883         sfree(unwcfname);
884
885         if (!swcm)
886             return 0;
887
888         ret = 1;
889
890         while ( (newname = sftp_wildcard_get_filename(swcm)) != NULL ) {
891             cname = canonify(newname);
892             if (!cname) {
893                 printf("%s: canonify: %s\n", newname, fxp_error());
894                 ret = 0;
895             }
896             matched = TRUE;
897             ret &= func(ctx, cname);
898             sfree(cname);
899         }
900
901         if (!matched) {
902             /* Politely warn the user that nothing matched. */
903             printf("%s: nothing matched\n", filename);
904         }
905
906         sftp_finish_wildcard_matching(swcm);
907     } else {
908         cname = canonify(unwcfname);
909         if (!cname) {
910             printf("%s: canonify: %s\n", filename, fxp_error());
911             ret = 0;
912         }
913         ret = func(ctx, cname);
914         sfree(cname);
915         sfree(unwcfname);
916     }
917
918     return ret;
919 }
920
921 /*
922  * Handy helper function.
923  */
924 int is_wildcard(char *name)
925 {
926     char *unwcfname = snewn(strlen(name)+1, char);
927     int is_wc = !wc_unescape(unwcfname, name);
928     sfree(unwcfname);
929     return is_wc;
930 }
931
932 /* ----------------------------------------------------------------------
933  * Actual sftp commands.
934  */
935 struct sftp_command {
936     char **words;
937     int nwords, wordssize;
938     int (*obey) (struct sftp_command *);        /* returns <0 to quit */
939 };
940
941 int sftp_cmd_null(struct sftp_command *cmd)
942 {
943     return 1;                          /* success */
944 }
945
946 int sftp_cmd_unknown(struct sftp_command *cmd)
947 {
948     printf("psftp: unknown command \"%s\"\n", cmd->words[0]);
949     return 0;                          /* failure */
950 }
951
952 int sftp_cmd_quit(struct sftp_command *cmd)
953 {
954     return -1;
955 }
956
957 int sftp_cmd_close(struct sftp_command *cmd)
958 {
959     if (back == NULL) {
960         not_connected();
961         return 0;
962     }
963
964     if (back != NULL && back->connected(backhandle)) {
965         char ch;
966         back->special(backhandle, TS_EOF);
967         sftp_recvdata(&ch, 1);
968     }
969     do_sftp_cleanup();
970
971     return 0;
972 }
973
974 /*
975  * List a directory. If no arguments are given, list pwd; otherwise
976  * list the directory given in words[1].
977  */
978 int sftp_cmd_ls(struct sftp_command *cmd)
979 {
980     struct fxp_handle *dirh;
981     struct fxp_names *names;
982     struct fxp_name **ournames;
983     int nnames, namesize;
984     char *dir, *cdir, *unwcdir, *wildcard;
985     struct sftp_packet *pktin;
986     struct sftp_request *req, *rreq;
987     int i;
988
989     if (back == NULL) {
990         not_connected();
991         return 0;
992     }
993
994     if (cmd->nwords < 2)
995         dir = ".";
996     else
997         dir = cmd->words[1];
998
999     unwcdir = snewn(1 + strlen(dir), char);
1000     if (wc_unescape(unwcdir, dir)) {
1001         dir = unwcdir;
1002         wildcard = NULL;
1003     } else {
1004         char *tmpdir;
1005         int len, check;
1006
1007         wildcard = stripslashes(dir, 0);
1008         unwcdir = dupstr(dir);
1009         len = wildcard - dir;
1010         unwcdir[len] = '\0';
1011         if (len > 0 && unwcdir[len-1] == '/')
1012             unwcdir[len-1] = '\0';
1013         tmpdir = snewn(1 + len, char);
1014         check = wc_unescape(tmpdir, unwcdir);
1015         sfree(tmpdir);
1016         if (!check) {
1017             printf("Multiple-level wildcards are not supported\n");
1018             sfree(unwcdir);
1019             return 0;
1020         }
1021         dir = unwcdir;
1022     }
1023
1024     cdir = canonify(dir);
1025     if (!cdir) {
1026         printf("%s: canonify: %s\n", dir, fxp_error());
1027         sfree(unwcdir);
1028         return 0;
1029     }
1030
1031     printf("Listing directory %s\n", cdir);
1032
1033     sftp_register(req = fxp_opendir_send(cdir));
1034     rreq = sftp_find_request(pktin = sftp_recv());
1035     assert(rreq == req);
1036     dirh = fxp_opendir_recv(pktin, rreq);
1037
1038     if (dirh == NULL) {
1039         printf("Unable to open %s: %s\n", dir, fxp_error());
1040     } else {
1041         nnames = namesize = 0;
1042         ournames = NULL;
1043
1044         while (1) {
1045
1046             sftp_register(req = fxp_readdir_send(dirh));
1047             rreq = sftp_find_request(pktin = sftp_recv());
1048             assert(rreq == req);
1049             names = fxp_readdir_recv(pktin, rreq);
1050
1051             if (names == NULL) {
1052                 if (fxp_error_type() == SSH_FX_EOF)
1053                     break;
1054                 printf("Reading directory %s: %s\n", dir, fxp_error());
1055                 break;
1056             }
1057             if (names->nnames == 0) {
1058                 fxp_free_names(names);
1059                 break;
1060             }
1061
1062             if (nnames + names->nnames >= namesize) {
1063                 namesize += names->nnames + 128;
1064                 ournames = sresize(ournames, namesize, struct fxp_name *);
1065             }
1066
1067             for (i = 0; i < names->nnames; i++)
1068                 if (!wildcard || wc_match(wildcard, names->names[i].filename))
1069                     ournames[nnames++] = fxp_dup_name(&names->names[i]);
1070
1071             fxp_free_names(names);
1072         }
1073         sftp_register(req = fxp_close_send(dirh));
1074         rreq = sftp_find_request(pktin = sftp_recv());
1075         assert(rreq == req);
1076         fxp_close_recv(pktin, rreq);
1077
1078         /*
1079          * Now we have our filenames. Sort them by actual file
1080          * name, and then output the longname parts.
1081          */
1082         qsort(ournames, nnames, sizeof(*ournames), sftp_name_compare);
1083
1084         /*
1085          * And print them.
1086          */
1087         for (i = 0; i < nnames; i++) {
1088             printf("%s\n", ournames[i]->longname);
1089             fxp_free_name(ournames[i]);
1090         }
1091         sfree(ournames);
1092     }
1093
1094     sfree(cdir);
1095     sfree(unwcdir);
1096
1097     return 1;
1098 }
1099
1100 /*
1101  * Change directories. We do this by canonifying the new name, then
1102  * trying to OPENDIR it. Only if that succeeds do we set the new pwd.
1103  */
1104 int sftp_cmd_cd(struct sftp_command *cmd)
1105 {
1106     struct fxp_handle *dirh;
1107     struct sftp_packet *pktin;
1108     struct sftp_request *req, *rreq;
1109     char *dir;
1110
1111     if (back == NULL) {
1112         not_connected();
1113         return 0;
1114     }
1115
1116     if (cmd->nwords < 2)
1117         dir = dupstr(homedir);
1118     else
1119         dir = canonify(cmd->words[1]);
1120
1121     if (!dir) {
1122         printf("%s: canonify: %s\n", dir, fxp_error());
1123         return 0;
1124     }
1125
1126     sftp_register(req = fxp_opendir_send(dir));
1127     rreq = sftp_find_request(pktin = sftp_recv());
1128     assert(rreq == req);
1129     dirh = fxp_opendir_recv(pktin, rreq);
1130
1131     if (!dirh) {
1132         printf("Directory %s: %s\n", dir, fxp_error());
1133         sfree(dir);
1134         return 0;
1135     }
1136
1137     sftp_register(req = fxp_close_send(dirh));
1138     rreq = sftp_find_request(pktin = sftp_recv());
1139     assert(rreq == req);
1140     fxp_close_recv(pktin, rreq);
1141
1142     sfree(pwd);
1143     pwd = dir;
1144     printf("Remote directory is now %s\n", pwd);
1145
1146     return 1;
1147 }
1148
1149 /*
1150  * Print current directory. Easy as pie.
1151  */
1152 int sftp_cmd_pwd(struct sftp_command *cmd)
1153 {
1154     if (back == NULL) {
1155         not_connected();
1156         return 0;
1157     }
1158
1159     printf("Remote directory is %s\n", pwd);
1160     return 1;
1161 }
1162
1163 /*
1164  * Get a file and save it at the local end. We have three very
1165  * similar commands here. The basic one is `get'; `reget' differs
1166  * in that it checks for the existence of the destination file and
1167  * starts from where a previous aborted transfer left off; `mget'
1168  * differs in that it interprets all its arguments as files to
1169  * transfer (never as a different local name for a remote file) and
1170  * can handle wildcards.
1171  */
1172 int sftp_general_get(struct sftp_command *cmd, int restart, int multiple)
1173 {
1174     char *fname, *unwcfname, *origfname, *origwfname, *outfname;
1175     int i, ret;
1176     int recurse = FALSE;
1177
1178     if (back == NULL) {
1179         not_connected();
1180         return 0;
1181     }
1182
1183     i = 1;
1184     while (i < cmd->nwords && cmd->words[i][0] == '-') {
1185         if (!strcmp(cmd->words[i], "--")) {
1186             /* finish processing options */
1187             i++;
1188             break;
1189         } else if (!strcmp(cmd->words[i], "-r")) {
1190             recurse = TRUE;
1191         } else {
1192             printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);
1193             return 0;
1194         }
1195         i++;
1196     }
1197
1198     if (i >= cmd->nwords) {
1199         printf("%s: expects a filename\n", cmd->words[0]);
1200         return 0;
1201     }
1202
1203     ret = 1;
1204     do {
1205         SftpWildcardMatcher *swcm;
1206
1207         origfname = cmd->words[i++];
1208         unwcfname = snewn(strlen(origfname)+1, char);
1209
1210         if (multiple && !wc_unescape(unwcfname, origfname)) {
1211             swcm = sftp_begin_wildcard_matching(origfname);
1212             if (!swcm) {
1213                 sfree(unwcfname);
1214                 continue;
1215             }
1216             origwfname = sftp_wildcard_get_filename(swcm);
1217             if (!origwfname) {
1218                 /* Politely warn the user that nothing matched. */
1219                 printf("%s: nothing matched\n", origfname);
1220                 sftp_finish_wildcard_matching(swcm);
1221                 sfree(unwcfname);
1222                 continue;
1223             }
1224         } else {
1225             origwfname = origfname;
1226             swcm = NULL;
1227         }
1228
1229         while (origwfname) {
1230             fname = canonify(origwfname);
1231
1232             if (!fname) {
1233                 printf("%s: canonify: %s\n", origwfname, fxp_error());
1234                 sfree(unwcfname);
1235                 return 0;
1236             }
1237
1238             if (!multiple && i < cmd->nwords)
1239                 outfname = cmd->words[i++];
1240             else
1241                 outfname = stripslashes(origwfname, 0);
1242
1243             ret = sftp_get_file(fname, outfname, recurse, restart);
1244
1245             sfree(fname);
1246
1247             if (swcm) {
1248                 sfree(origwfname);
1249                 origwfname = sftp_wildcard_get_filename(swcm);
1250             } else {
1251                 origwfname = NULL;
1252             }
1253         }
1254         sfree(unwcfname);
1255         if (swcm)
1256             sftp_finish_wildcard_matching(swcm);
1257         if (!ret)
1258             return ret;
1259
1260     } while (multiple && i < cmd->nwords);
1261
1262     return ret;
1263 }
1264 int sftp_cmd_get(struct sftp_command *cmd)
1265 {
1266     return sftp_general_get(cmd, 0, 0);
1267 }
1268 int sftp_cmd_mget(struct sftp_command *cmd)
1269 {
1270     return sftp_general_get(cmd, 0, 1);
1271 }
1272 int sftp_cmd_reget(struct sftp_command *cmd)
1273 {
1274     return sftp_general_get(cmd, 1, 0);
1275 }
1276
1277 /*
1278  * Send a file and store it at the remote end. We have three very
1279  * similar commands here. The basic one is `put'; `reput' differs
1280  * in that it checks for the existence of the destination file and
1281  * starts from where a previous aborted transfer left off; `mput'
1282  * differs in that it interprets all its arguments as files to
1283  * transfer (never as a different remote name for a local file) and
1284  * can handle wildcards.
1285  */
1286 int sftp_general_put(struct sftp_command *cmd, int restart, int multiple)
1287 {
1288     char *fname, *wfname, *origoutfname, *outfname;
1289     int i, ret;
1290     int recurse = FALSE;
1291
1292     if (back == NULL) {
1293         not_connected();
1294         return 0;
1295     }
1296
1297     i = 1;
1298     while (i < cmd->nwords && cmd->words[i][0] == '-') {
1299         if (!strcmp(cmd->words[i], "--")) {
1300             /* finish processing options */
1301             i++;
1302             break;
1303         } else if (!strcmp(cmd->words[i], "-r")) {
1304             recurse = TRUE;
1305         } else {
1306             printf("%s: unrecognised option '%s'\n", cmd->words[0], cmd->words[i]);
1307             return 0;
1308         }
1309         i++;
1310     }
1311
1312     if (i >= cmd->nwords) {
1313         printf("%s: expects a filename\n", cmd->words[0]);
1314         return 0;
1315     }
1316
1317     ret = 1;
1318     do {
1319         WildcardMatcher *wcm;
1320         fname = cmd->words[i++];
1321
1322         if (multiple && test_wildcard(fname, FALSE) == WCTYPE_WILDCARD) {
1323             wcm = begin_wildcard_matching(fname);
1324             wfname = wildcard_get_filename(wcm);
1325             if (!wfname) {
1326                 /* Politely warn the user that nothing matched. */
1327                 printf("%s: nothing matched\n", fname);
1328                 finish_wildcard_matching(wcm);
1329                 continue;
1330             }
1331         } else {
1332             wfname = fname;
1333             wcm = NULL;
1334         }
1335
1336         while (wfname) {
1337             if (!multiple && i < cmd->nwords)
1338                 origoutfname = cmd->words[i++];
1339             else
1340                 origoutfname = stripslashes(wfname, 1);
1341
1342             outfname = canonify(origoutfname);
1343             if (!outfname) {
1344                 printf("%s: canonify: %s\n", origoutfname, fxp_error());
1345                 if (wcm) {
1346                     sfree(wfname);
1347                     finish_wildcard_matching(wcm);
1348                 }
1349                 return 0;
1350             }
1351             ret = sftp_put_file(wfname, outfname, recurse, restart);
1352             sfree(outfname);
1353
1354             if (wcm) {
1355                 sfree(wfname);
1356                 wfname = wildcard_get_filename(wcm);
1357             } else {
1358                 wfname = NULL;
1359             }
1360         }
1361
1362         if (wcm)
1363             finish_wildcard_matching(wcm);
1364
1365         if (!ret)
1366             return ret;
1367
1368     } while (multiple && i < cmd->nwords);
1369
1370     return ret;
1371 }
1372 int sftp_cmd_put(struct sftp_command *cmd)
1373 {
1374     return sftp_general_put(cmd, 0, 0);
1375 }
1376 int sftp_cmd_mput(struct sftp_command *cmd)
1377 {
1378     return sftp_general_put(cmd, 0, 1);
1379 }
1380 int sftp_cmd_reput(struct sftp_command *cmd)
1381 {
1382     return sftp_general_put(cmd, 1, 0);
1383 }
1384
1385 int sftp_cmd_mkdir(struct sftp_command *cmd)
1386 {
1387     char *dir;
1388     struct sftp_packet *pktin;
1389     struct sftp_request *req, *rreq;
1390     int result;
1391     int i, ret;
1392
1393     if (back == NULL) {
1394         not_connected();
1395         return 0;
1396     }
1397
1398     if (cmd->nwords < 2) {
1399         printf("mkdir: expects a directory\n");
1400         return 0;
1401     }
1402
1403     ret = 1;
1404     for (i = 1; i < cmd->nwords; i++) {
1405         dir = canonify(cmd->words[i]);
1406         if (!dir) {
1407             printf("%s: canonify: %s\n", dir, fxp_error());
1408             return 0;
1409         }
1410
1411         sftp_register(req = fxp_mkdir_send(dir));
1412         rreq = sftp_find_request(pktin = sftp_recv());
1413         assert(rreq == req);
1414         result = fxp_mkdir_recv(pktin, rreq);
1415
1416         if (!result) {
1417             printf("mkdir %s: %s\n", dir, fxp_error());
1418             ret = 0;
1419         } else
1420             printf("mkdir %s: OK\n", dir);
1421
1422         sfree(dir);
1423     }
1424
1425     return ret;
1426 }
1427
1428 static int sftp_action_rmdir(void *vctx, char *dir)
1429 {
1430     struct sftp_packet *pktin;
1431     struct sftp_request *req, *rreq;
1432     int result;
1433
1434     sftp_register(req = fxp_rmdir_send(dir));
1435     rreq = sftp_find_request(pktin = sftp_recv());
1436     assert(rreq == req);
1437     result = fxp_rmdir_recv(pktin, rreq);
1438
1439     if (!result) {
1440         printf("rmdir %s: %s\n", dir, fxp_error());
1441         return 0;
1442     }
1443
1444     printf("rmdir %s: OK\n", dir);
1445
1446     return 1;
1447 }
1448
1449 int sftp_cmd_rmdir(struct sftp_command *cmd)
1450 {
1451     int i, ret;
1452
1453     if (back == NULL) {
1454         not_connected();
1455         return 0;
1456     }
1457
1458     if (cmd->nwords < 2) {
1459         printf("rmdir: expects a directory\n");
1460         return 0;
1461     }
1462
1463     ret = 1;
1464     for (i = 1; i < cmd->nwords; i++)
1465         ret &= wildcard_iterate(cmd->words[i], sftp_action_rmdir, NULL);
1466
1467     return ret;
1468 }
1469
1470 static int sftp_action_rm(void *vctx, char *fname)
1471 {
1472     struct sftp_packet *pktin;
1473     struct sftp_request *req, *rreq;
1474     int result;
1475
1476     sftp_register(req = fxp_remove_send(fname));
1477     rreq = sftp_find_request(pktin = sftp_recv());
1478     assert(rreq == req);
1479     result = fxp_remove_recv(pktin, rreq);
1480
1481     if (!result) {
1482         printf("rm %s: %s\n", fname, fxp_error());
1483         return 0;
1484     }
1485
1486     printf("rm %s: OK\n", fname);
1487
1488     return 1;
1489 }
1490
1491 int sftp_cmd_rm(struct sftp_command *cmd)
1492 {
1493     int i, ret;
1494
1495     if (back == NULL) {
1496         not_connected();
1497         return 0;
1498     }
1499
1500     if (cmd->nwords < 2) {
1501         printf("rm: expects a filename\n");
1502         return 0;
1503     }
1504
1505     ret = 1;
1506     for (i = 1; i < cmd->nwords; i++)
1507         ret &= wildcard_iterate(cmd->words[i], sftp_action_rm, NULL);
1508
1509     return ret;
1510 }
1511
1512 static int check_is_dir(char *dstfname)
1513 {
1514     struct sftp_packet *pktin;
1515     struct sftp_request *req, *rreq;
1516     struct fxp_attrs attrs;
1517     int result;
1518
1519     sftp_register(req = fxp_stat_send(dstfname));
1520     rreq = sftp_find_request(pktin = sftp_recv());
1521     assert(rreq == req);
1522     result = fxp_stat_recv(pktin, rreq, &attrs);
1523
1524     if (result &&
1525         (attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) &&
1526         (attrs.permissions & 0040000))
1527         return TRUE;
1528     else
1529         return FALSE;
1530 }
1531
1532 struct sftp_context_mv {
1533     char *dstfname;
1534     int dest_is_dir;
1535 };
1536
1537 static int sftp_action_mv(void *vctx, char *srcfname)
1538 {
1539     struct sftp_context_mv *ctx = (struct sftp_context_mv *)vctx;
1540     struct sftp_packet *pktin;
1541     struct sftp_request *req, *rreq;
1542     const char *error;
1543     char *finalfname, *newcanon = NULL;
1544     int ret, result;
1545
1546     if (ctx->dest_is_dir) {
1547         char *p;
1548         char *newname;
1549
1550         p = srcfname + strlen(srcfname);
1551         while (p > srcfname && p[-1] != '/') p--;
1552         newname = dupcat(ctx->dstfname, "/", p, NULL);
1553         newcanon = canonify(newname);
1554         if (!newcanon) {
1555             printf("%s: canonify: %s\n", newname, fxp_error());
1556             sfree(newname);
1557             return 0;
1558         }
1559         sfree(newname);
1560
1561         finalfname = newcanon;
1562     } else {
1563         finalfname = ctx->dstfname;
1564     }
1565
1566     sftp_register(req = fxp_rename_send(srcfname, finalfname));
1567     rreq = sftp_find_request(pktin = sftp_recv());
1568     assert(rreq == req);
1569     result = fxp_rename_recv(pktin, rreq);
1570
1571     error = result ? NULL : fxp_error();
1572
1573     if (error) {
1574         printf("mv %s %s: %s\n", srcfname, finalfname, error);
1575         ret = 0;
1576     } else {
1577         printf("%s -> %s\n", srcfname, finalfname);
1578         ret = 1;
1579     }
1580
1581     sfree(newcanon);
1582     return ret;
1583 }
1584
1585 int sftp_cmd_mv(struct sftp_command *cmd)
1586 {
1587     struct sftp_context_mv actx, *ctx = &actx;
1588     int i, ret;
1589
1590     if (back == NULL) {
1591         not_connected();
1592         return 0;
1593     }
1594
1595     if (cmd->nwords < 3) {
1596         printf("mv: expects two filenames\n");
1597         return 0;
1598     }
1599
1600     ctx->dstfname = canonify(cmd->words[cmd->nwords-1]);
1601     if (!ctx->dstfname) {
1602         printf("%s: canonify: %s\n", ctx->dstfname, fxp_error());
1603         return 0;
1604     }
1605
1606     /*
1607      * If there's more than one source argument, or one source
1608      * argument which is a wildcard, we _require_ that the
1609      * destination is a directory.
1610      */
1611     ctx->dest_is_dir = check_is_dir(ctx->dstfname);
1612     if ((cmd->nwords > 3 || is_wildcard(cmd->words[1])) && !ctx->dest_is_dir) {
1613         printf("mv: multiple or wildcard arguments require the destination"
1614                " to be a directory\n");
1615         sfree(ctx->dstfname);
1616         return 0;
1617     }
1618
1619     /*
1620      * Now iterate over the source arguments.
1621      */
1622     ret = 1;
1623     for (i = 1; i < cmd->nwords-1; i++)
1624         ret &= wildcard_iterate(cmd->words[i], sftp_action_mv, ctx);
1625
1626     sfree(ctx->dstfname);
1627     return ret;
1628 }
1629
1630 struct sftp_context_chmod {
1631     unsigned attrs_clr, attrs_xor;
1632 };
1633
1634 static int sftp_action_chmod(void *vctx, char *fname)
1635 {
1636     struct fxp_attrs attrs;
1637     struct sftp_packet *pktin;
1638     struct sftp_request *req, *rreq;
1639     int result;
1640     unsigned oldperms, newperms;
1641     struct sftp_context_chmod *ctx = (struct sftp_context_chmod *)vctx;
1642
1643     sftp_register(req = fxp_stat_send(fname));
1644     rreq = sftp_find_request(pktin = sftp_recv());
1645     assert(rreq == req);
1646     result = fxp_stat_recv(pktin, rreq, &attrs);
1647
1648     if (!result || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
1649         printf("get attrs for %s: %s\n", fname,
1650                result ? "file permissions not provided" : fxp_error());
1651         return 0;
1652     }
1653
1654     attrs.flags = SSH_FILEXFER_ATTR_PERMISSIONS;   /* perms _only_ */
1655     oldperms = attrs.permissions & 07777;
1656     attrs.permissions &= ~ctx->attrs_clr;
1657     attrs.permissions ^= ctx->attrs_xor;
1658     newperms = attrs.permissions & 07777;
1659
1660     if (oldperms == newperms)
1661         return 1;                      /* no need to do anything! */
1662
1663     sftp_register(req = fxp_setstat_send(fname, attrs));
1664     rreq = sftp_find_request(pktin = sftp_recv());
1665     assert(rreq == req);
1666     result = fxp_setstat_recv(pktin, rreq);
1667
1668     if (!result) {
1669         printf("set attrs for %s: %s\n", fname, fxp_error());
1670         return 0;
1671     }
1672
1673     printf("%s: %04o -> %04o\n", fname, oldperms, newperms);
1674
1675     return 1;
1676 }
1677
1678 int sftp_cmd_chmod(struct sftp_command *cmd)
1679 {
1680     char *mode;
1681     int i, ret;
1682     struct sftp_context_chmod actx, *ctx = &actx;
1683
1684     if (back == NULL) {
1685         not_connected();
1686         return 0;
1687     }
1688
1689     if (cmd->nwords < 3) {
1690         printf("chmod: expects a mode specifier and a filename\n");
1691         return 0;
1692     }
1693
1694     /*
1695      * Attempt to parse the mode specifier in cmd->words[1]. We
1696      * don't support the full horror of Unix chmod; instead we
1697      * support a much simpler syntax in which the user can either
1698      * specify an octal number, or a comma-separated sequence of
1699      * [ugoa]*[-+=][rwxst]+. (The initial [ugoa] sequence may
1700      * _only_ be omitted if the only attribute mentioned is t,
1701      * since all others require a user/group/other specification.
1702      * Additionally, the s attribute may not be specified for any
1703      * [ugoa] specifications other than exactly u or exactly g.
1704      */
1705     ctx->attrs_clr = ctx->attrs_xor = 0;
1706     mode = cmd->words[1];
1707     if (mode[0] >= '0' && mode[0] <= '9') {
1708         if (mode[strspn(mode, "01234567")]) {
1709             printf("chmod: numeric file modes should"
1710                    " contain digits 0-7 only\n");
1711             return 0;
1712         }
1713         ctx->attrs_clr = 07777;
1714         sscanf(mode, "%o", &ctx->attrs_xor);
1715         ctx->attrs_xor &= ctx->attrs_clr;
1716     } else {
1717         while (*mode) {
1718             char *modebegin = mode;
1719             unsigned subset, perms;
1720             int action;
1721
1722             subset = 0;
1723             while (*mode && *mode != ',' &&
1724                    *mode != '+' && *mode != '-' && *mode != '=') {
1725                 switch (*mode) {
1726                   case 'u': subset |= 04700; break; /* setuid, user perms */
1727                   case 'g': subset |= 02070; break; /* setgid, group perms */
1728                   case 'o': subset |= 00007; break; /* just other perms */
1729                   case 'a': subset |= 06777; break; /* all of the above */
1730                   default:
1731                     printf("chmod: file mode '%.*s' contains unrecognised"
1732                            " user/group/other specifier '%c'\n",
1733                            (int)strcspn(modebegin, ","), modebegin, *mode);
1734                     return 0;
1735                 }
1736                 mode++;
1737             }
1738             if (!*mode || *mode == ',') {
1739                 printf("chmod: file mode '%.*s' is incomplete\n",
1740                        (int)strcspn(modebegin, ","), modebegin);
1741                 return 0;
1742             }
1743             action = *mode++;
1744             if (!*mode || *mode == ',') {
1745                 printf("chmod: file mode '%.*s' is incomplete\n",
1746                        (int)strcspn(modebegin, ","), modebegin);
1747                 return 0;
1748             }
1749             perms = 0;
1750             while (*mode && *mode != ',') {
1751                 switch (*mode) {
1752                   case 'r': perms |= 00444; break;
1753                   case 'w': perms |= 00222; break;
1754                   case 'x': perms |= 00111; break;
1755                   case 't': perms |= 01000; subset |= 01000; break;
1756                   case 's':
1757                     if ((subset & 06777) != 04700 &&
1758                         (subset & 06777) != 02070) {
1759                         printf("chmod: file mode '%.*s': set[ug]id bit should"
1760                                " be used with exactly one of u or g only\n",
1761                                (int)strcspn(modebegin, ","), modebegin);
1762                         return 0;
1763                     }
1764                     perms |= 06000;
1765                     break;
1766                   default:
1767                     printf("chmod: file mode '%.*s' contains unrecognised"
1768                            " permission specifier '%c'\n",
1769                            (int)strcspn(modebegin, ","), modebegin, *mode);
1770                     return 0;
1771                 }
1772                 mode++;
1773             }
1774             if (!(subset & 06777) && (perms &~ subset)) {
1775                 printf("chmod: file mode '%.*s' contains no user/group/other"
1776                        " specifier and permissions other than 't' \n",
1777                        (int)strcspn(modebegin, ","), modebegin);
1778                 return 0;
1779             }
1780             perms &= subset;
1781             switch (action) {
1782               case '+':
1783                 ctx->attrs_clr |= perms;
1784                 ctx->attrs_xor |= perms;
1785                 break;
1786               case '-':
1787                 ctx->attrs_clr |= perms;
1788                 ctx->attrs_xor &= ~perms;
1789                 break;
1790               case '=':
1791                 ctx->attrs_clr |= subset;
1792                 ctx->attrs_xor |= perms;
1793                 break;
1794             }
1795             if (*mode) mode++;         /* eat comma */
1796         }
1797     }
1798
1799     ret = 1;
1800     for (i = 2; i < cmd->nwords; i++)
1801         ret &= wildcard_iterate(cmd->words[i], sftp_action_chmod, ctx);
1802
1803     return ret;
1804 }
1805
1806 static int sftp_cmd_open(struct sftp_command *cmd)
1807 {
1808     int portnumber;
1809
1810     if (back != NULL) {
1811         printf("psftp: already connected\n");
1812         return 0;
1813     }
1814
1815     if (cmd->nwords < 2) {
1816         printf("open: expects a host name\n");
1817         return 0;
1818     }
1819
1820     if (cmd->nwords > 2) {
1821         portnumber = atoi(cmd->words[2]);
1822         if (portnumber == 0) {
1823             printf("open: invalid port number\n");
1824             return 0;
1825         }
1826     } else
1827         portnumber = 0;
1828
1829     if (psftp_connect(cmd->words[1], NULL, portnumber)) {
1830         back = NULL;                   /* connection is already closed */
1831         return -1;                     /* this is fatal */
1832     }
1833     do_sftp_init();
1834     return 1;
1835 }
1836
1837 static int sftp_cmd_lcd(struct sftp_command *cmd)
1838 {
1839     char *currdir, *errmsg;
1840
1841     if (cmd->nwords < 2) {
1842         printf("lcd: expects a local directory name\n");
1843         return 0;
1844     }
1845
1846     errmsg = psftp_lcd(cmd->words[1]);
1847     if (errmsg) {
1848         printf("lcd: unable to change directory: %s\n", errmsg);
1849         sfree(errmsg);
1850         return 0;
1851     }
1852
1853     currdir = psftp_getcwd();
1854     printf("New local directory is %s\n", currdir);
1855     sfree(currdir);
1856
1857     return 1;
1858 }
1859
1860 static int sftp_cmd_lpwd(struct sftp_command *cmd)
1861 {
1862     char *currdir;
1863
1864     currdir = psftp_getcwd();
1865     printf("Current local directory is %s\n", currdir);
1866     sfree(currdir);
1867
1868     return 1;
1869 }
1870
1871 static int sftp_cmd_pling(struct sftp_command *cmd)
1872 {
1873     int exitcode;
1874
1875     exitcode = system(cmd->words[1]);
1876     return (exitcode == 0);
1877 }
1878
1879 static int sftp_cmd_help(struct sftp_command *cmd);
1880
1881 static struct sftp_cmd_lookup {
1882     char *name;
1883     /*
1884      * For help purposes, there are two kinds of command:
1885      * 
1886      *  - primary commands, in which `longhelp' is non-NULL. In
1887      *    this case `shorthelp' is descriptive text, and `longhelp'
1888      *    is longer descriptive text intended to be printed after
1889      *    the command name.
1890      * 
1891      *  - alias commands, in which `longhelp' is NULL. In this case
1892      *    `shorthelp' is the name of a primary command, which
1893      *    contains the help that should double up for this command.
1894      */
1895     int listed;                        /* do we list this in primary help? */
1896     char *shorthelp;
1897     char *longhelp;
1898     int (*obey) (struct sftp_command *);
1899 } sftp_lookup[] = {
1900     /*
1901      * List of sftp commands. This is binary-searched so it MUST be
1902      * in ASCII order.
1903      */
1904     {
1905         "!", TRUE, "run a local command",
1906             "<command>\n"
1907             /* FIXME: this example is crap for non-Windows. */
1908             "  Runs a local command. For example, \"!del myfile\".\n",
1909             sftp_cmd_pling
1910     },
1911     {
1912         "bye", TRUE, "finish your SFTP session",
1913             "\n"
1914             "  Terminates your SFTP session and quits the PSFTP program.\n",
1915             sftp_cmd_quit
1916     },
1917     {
1918         "cd", TRUE, "change your remote working directory",
1919             " [ <new working directory> ]\n"
1920             "  Change the remote working directory for your SFTP session.\n"
1921             "  If a new working directory is not supplied, you will be\n"
1922             "  returned to your home directory.\n",
1923             sftp_cmd_cd
1924     },
1925     {
1926         "chmod", TRUE, "change file permissions and modes",
1927             " <modes> <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
1928             "  Change the file permissions on one or more remote files or\n"
1929             "  directories.\n"
1930             "  <modes> can be any octal Unix permission specifier.\n"
1931             "  Alternatively, <modes> can include the following modifiers:\n"
1932             "    u+r     make file readable by owning user\n"
1933             "    u+w     make file writable by owning user\n"
1934             "    u+x     make file executable by owning user\n"
1935             "    u-r     make file not readable by owning user\n"
1936             "    [also u-w, u-x]\n"
1937             "    g+r     make file readable by members of owning group\n"
1938             "    [also g+w, g+x, g-r, g-w, g-x]\n"
1939             "    o+r     make file readable by all other users\n"
1940             "    [also o+w, o+x, o-r, o-w, o-x]\n"
1941             "    a+r     make file readable by absolutely everybody\n"
1942             "    [also a+w, a+x, a-r, a-w, a-x]\n"
1943             "    u+s     enable the Unix set-user-ID bit\n"
1944             "    u-s     disable the Unix set-user-ID bit\n"
1945             "    g+s     enable the Unix set-group-ID bit\n"
1946             "    g-s     disable the Unix set-group-ID bit\n"
1947             "    +t      enable the Unix \"sticky bit\"\n"
1948             "  You can give more than one modifier for the same user (\"g-rwx\"), and\n"
1949             "  more than one user for the same modifier (\"ug+w\"). You can\n"
1950             "  use commas to separate different modifiers (\"u+rwx,g+s\").\n",
1951             sftp_cmd_chmod
1952     },
1953     {
1954         "close", TRUE, "finish your SFTP session but do not quit PSFTP",
1955             "\n"
1956             "  Terminates your SFTP session, but does not quit the PSFTP\n"
1957             "  program. You can then use \"open\" to start another SFTP\n"
1958             "  session, to the same server or to a different one.\n",
1959             sftp_cmd_close
1960     },
1961     {
1962         "del", TRUE, "delete files on the remote server",
1963             " <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
1964             "  Delete a file or files from the server.\n",
1965             sftp_cmd_rm
1966     },
1967     {
1968         "delete", FALSE, "del", NULL, sftp_cmd_rm
1969     },
1970     {
1971         "dir", TRUE, "list remote files",
1972             " [ <directory-name> ]/[ <wildcard> ]\n"
1973             "  List the contents of a specified directory on the server.\n"
1974             "  If <directory-name> is not given, the current working directory\n"
1975             "  is assumed.\n"
1976             "  If <wildcard> is given, it is treated as a set of files to\n"
1977             "  list; otherwise, all files are listed.\n",
1978             sftp_cmd_ls
1979     },
1980     {
1981         "exit", TRUE, "bye", NULL, sftp_cmd_quit
1982     },
1983     {
1984         "get", TRUE, "download a file from the server to your local machine",
1985             " [ -r ] [ -- ] <filename> [ <local-filename> ]\n"
1986             "  Downloads a file on the server and stores it locally under\n"
1987             "  the same name, or under a different one if you supply the\n"
1988             "  argument <local-filename>.\n"
1989             "  If -r specified, recursively fetch a directory.\n",
1990             sftp_cmd_get
1991     },
1992     {
1993         "help", TRUE, "give help",
1994             " [ <command> [ <command> ... ] ]\n"
1995             "  Give general help if no commands are specified.\n"
1996             "  If one or more commands are specified, give specific help on\n"
1997             "  those particular commands.\n",
1998             sftp_cmd_help
1999     },
2000     {
2001         "lcd", TRUE, "change local working directory",
2002             " <local-directory-name>\n"
2003             "  Change the local working directory of the PSFTP program (the\n"
2004             "  default location where the \"get\" command will save files).\n",
2005             sftp_cmd_lcd
2006     },
2007     {
2008         "lpwd", TRUE, "print local working directory",
2009             "\n"
2010             "  Print the local working directory of the PSFTP program (the\n"
2011             "  default location where the \"get\" command will save files).\n",
2012             sftp_cmd_lpwd
2013     },
2014     {
2015         "ls", TRUE, "dir", NULL,
2016             sftp_cmd_ls
2017     },
2018     {
2019         "mget", TRUE, "download multiple files at once",
2020             " [ -r ] [ -- ] <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
2021             "  Downloads many files from the server, storing each one under\n"
2022             "  the same name it has on the server side. You can use wildcards\n"
2023             "  such as \"*.c\" to specify lots of files at once.\n"
2024             "  If -r specified, recursively fetch files and directories.\n",
2025             sftp_cmd_mget
2026     },
2027     {
2028         "mkdir", TRUE, "create directories on the remote server",
2029             " <directory-name> [ <directory-name>... ]\n"
2030             "  Creates directories with the given names on the server.\n",
2031             sftp_cmd_mkdir
2032     },
2033     {
2034         "mput", TRUE, "upload multiple files at once",
2035             " [ -r ] [ -- ] <filename-or-wildcard> [ <filename-or-wildcard>... ]\n"
2036             "  Uploads many files to the server, storing each one under the\n"
2037             "  same name it has on the client side. You can use wildcards\n"
2038             "  such as \"*.c\" to specify lots of files at once.\n"
2039             "  If -r specified, recursively store files and directories.\n",
2040             sftp_cmd_mput
2041     },
2042     {
2043         "mv", TRUE, "move or rename file(s) on the remote server",
2044             " <source> [ <source>... ] <destination>\n"
2045             "  Moves or renames <source>(s) on the server to <destination>,\n"
2046             "  also on the server.\n"
2047             "  If <destination> specifies an existing directory, then <source>\n"
2048             "  may be a wildcard, and multiple <source>s may be given; all\n"
2049             "  source files are moved into <destination>.\n"
2050             "  Otherwise, <source> must specify a single file, which is moved\n"
2051             "  or renamed so that it is accessible under the name <destination>.\n",
2052             sftp_cmd_mv
2053     },
2054     {
2055         "open", TRUE, "connect to a host",
2056             " [<user>@]<hostname> [<port>]\n"
2057             "  Establishes an SFTP connection to a given host. Only usable\n"
2058             "  when you are not already connected to a server.\n",
2059             sftp_cmd_open
2060     },
2061     {
2062         "put", TRUE, "upload a file from your local machine to the server",
2063             " [ -r ] [ -- ] <filename> [ <remote-filename> ]\n"
2064             "  Uploads a file to the server and stores it there under\n"
2065             "  the same name, or under a different one if you supply the\n"
2066             "  argument <remote-filename>.\n"
2067             "  If -r specified, recursively store a directory.\n",
2068             sftp_cmd_put
2069     },
2070     {
2071         "pwd", TRUE, "print your remote working directory",
2072             "\n"
2073             "  Print the current remote working directory for your SFTP session.\n",
2074             sftp_cmd_pwd
2075     },
2076     {
2077         "quit", TRUE, "bye", NULL,
2078             sftp_cmd_quit
2079     },
2080     {
2081         "reget", TRUE, "continue downloading files",
2082             " [ -r ] [ -- ] <filename> [ <local-filename> ]\n"
2083             "  Works exactly like the \"get\" command, but the local file\n"
2084             "  must already exist. The download will begin at the end of the\n"
2085             "  file. This is for resuming a download that was interrupted.\n"
2086             "  If -r specified, resume interrupted \"get -r\".\n",
2087             sftp_cmd_reget
2088     },
2089     {
2090         "ren", TRUE, "mv", NULL,
2091             sftp_cmd_mv
2092     },
2093     {
2094         "rename", FALSE, "mv", NULL,
2095             sftp_cmd_mv
2096     },
2097     {
2098         "reput", TRUE, "continue uploading files",
2099             " [ -r ] [ -- ] <filename> [ <remote-filename> ]\n"
2100             "  Works exactly like the \"put\" command, but the remote file\n"
2101             "  must already exist. The upload will begin at the end of the\n"
2102             "  file. This is for resuming an upload that was interrupted.\n"
2103             "  If -r specified, resume interrupted \"put -r\".\n",
2104             sftp_cmd_reput
2105     },
2106     {
2107         "rm", TRUE, "del", NULL,
2108             sftp_cmd_rm
2109     },
2110     {
2111         "rmdir", TRUE, "remove directories on the remote server",
2112             " <directory-name> [ <directory-name>... ]\n"
2113             "  Removes the directory with the given name on the server.\n"
2114             "  The directory will not be removed unless it is empty.\n"
2115             "  Wildcards may be used to specify multiple directories.\n",
2116             sftp_cmd_rmdir
2117     }
2118 };
2119
2120 const struct sftp_cmd_lookup *lookup_command(char *name)
2121 {
2122     int i, j, k, cmp;
2123
2124     i = -1;
2125     j = sizeof(sftp_lookup) / sizeof(*sftp_lookup);
2126     while (j - i > 1) {
2127         k = (j + i) / 2;
2128         cmp = strcmp(name, sftp_lookup[k].name);
2129         if (cmp < 0)
2130             j = k;
2131         else if (cmp > 0)
2132             i = k;
2133         else {
2134             return &sftp_lookup[k];
2135         }
2136     }
2137     return NULL;
2138 }
2139
2140 static int sftp_cmd_help(struct sftp_command *cmd)
2141 {
2142     int i;
2143     if (cmd->nwords == 1) {
2144         /*
2145          * Give short help on each command.
2146          */
2147         int maxlen;
2148         maxlen = 0;
2149         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
2150             int len;
2151             if (!sftp_lookup[i].listed)
2152                 continue;
2153             len = strlen(sftp_lookup[i].name);
2154             if (maxlen < len)
2155                 maxlen = len;
2156         }
2157         for (i = 0; i < sizeof(sftp_lookup) / sizeof(*sftp_lookup); i++) {
2158             const struct sftp_cmd_lookup *lookup;
2159             if (!sftp_lookup[i].listed)
2160                 continue;
2161             lookup = &sftp_lookup[i];
2162             printf("%-*s", maxlen+2, lookup->name);
2163             if (lookup->longhelp == NULL)
2164                 lookup = lookup_command(lookup->shorthelp);
2165             printf("%s\n", lookup->shorthelp);
2166         }
2167     } else {
2168         /*
2169          * Give long help on specific commands.
2170          */
2171         for (i = 1; i < cmd->nwords; i++) {
2172             const struct sftp_cmd_lookup *lookup;
2173             lookup = lookup_command(cmd->words[i]);
2174             if (!lookup) {
2175                 printf("help: %s: command not found\n", cmd->words[i]);
2176             } else {
2177                 printf("%s", lookup->name);
2178                 if (lookup->longhelp == NULL)
2179                     lookup = lookup_command(lookup->shorthelp);
2180                 printf("%s", lookup->longhelp);
2181             }
2182         }
2183     }
2184     return 1;
2185 }
2186
2187 /* ----------------------------------------------------------------------
2188  * Command line reading and parsing.
2189  */
2190 struct sftp_command *sftp_getcmd(FILE *fp, int mode, int modeflags)
2191 {
2192     char *line;
2193     struct sftp_command *cmd;
2194     char *p, *q, *r;
2195     int quoting;
2196
2197     cmd = snew(struct sftp_command);
2198     cmd->words = NULL;
2199     cmd->nwords = 0;
2200     cmd->wordssize = 0;
2201
2202     line = NULL;
2203
2204     if (fp) {
2205         if (modeflags & 1)
2206             printf("psftp> ");
2207         line = fgetline(fp);
2208     } else {
2209         line = ssh_sftp_get_cmdline("psftp> ", back == NULL);
2210     }
2211
2212     if (!line || !*line) {
2213         cmd->obey = sftp_cmd_quit;
2214         if ((mode == 0) || (modeflags & 1))
2215             printf("quit\n");
2216         return cmd;                    /* eof */
2217     }
2218
2219     line[strcspn(line, "\r\n")] = '\0';
2220
2221     if (modeflags & 1) {
2222         printf("%s\n", line);
2223     }
2224
2225     p = line;
2226     while (*p && (*p == ' ' || *p == '\t'))
2227         p++;
2228
2229     if (*p == '!') {
2230         /*
2231          * Special case: the ! command. This is always parsed as
2232          * exactly two words: one containing the !, and the second
2233          * containing everything else on the line.
2234          */
2235         cmd->nwords = cmd->wordssize = 2;
2236         cmd->words = sresize(cmd->words, cmd->wordssize, char *);
2237         cmd->words[0] = dupstr("!");
2238         cmd->words[1] = dupstr(p+1);
2239     } else {
2240
2241         /*
2242          * Parse the command line into words. The syntax is:
2243          *  - double quotes are removed, but cause spaces within to be
2244          *    treated as non-separating.
2245          *  - a double-doublequote pair is a literal double quote, inside
2246          *    _or_ outside quotes. Like this:
2247          *
2248          *      firstword "second word" "this has ""quotes"" in" and""this""
2249          *
2250          * becomes
2251          *
2252          *      >firstword<
2253          *      >second word<
2254          *      >this has "quotes" in<
2255          *      >and"this"<
2256          */
2257         while (*p) {
2258             /* skip whitespace */
2259             while (*p && (*p == ' ' || *p == '\t'))
2260                 p++;
2261             /* mark start of word */
2262             q = r = p;                 /* q sits at start, r writes word */
2263             quoting = 0;
2264             while (*p) {
2265                 if (!quoting && (*p == ' ' || *p == '\t'))
2266                     break;                     /* reached end of word */
2267                 else if (*p == '"' && p[1] == '"')
2268                     p += 2, *r++ = '"';    /* a literal quote */
2269                 else if (*p == '"')
2270                     p++, quoting = !quoting;
2271                 else
2272                     *r++ = *p++;
2273             }
2274             if (*p)
2275                 p++;                   /* skip over the whitespace */
2276             *r = '\0';
2277             if (cmd->nwords >= cmd->wordssize) {
2278                 cmd->wordssize = cmd->nwords + 16;
2279                 cmd->words = sresize(cmd->words, cmd->wordssize, char *);
2280             }
2281             cmd->words[cmd->nwords++] = dupstr(q);
2282         }
2283     }
2284
2285     sfree(line);
2286
2287     /*
2288      * Now parse the first word and assign a function.
2289      */
2290
2291     if (cmd->nwords == 0)
2292         cmd->obey = sftp_cmd_null;
2293     else {
2294         const struct sftp_cmd_lookup *lookup;
2295         lookup = lookup_command(cmd->words[0]);
2296         if (!lookup)
2297             cmd->obey = sftp_cmd_unknown;
2298         else
2299             cmd->obey = lookup->obey;
2300     }
2301
2302     return cmd;
2303 }
2304
2305 static int do_sftp_init(void)
2306 {
2307     struct sftp_packet *pktin;
2308     struct sftp_request *req, *rreq;
2309
2310     /*
2311      * Do protocol initialisation. 
2312      */
2313     if (!fxp_init()) {
2314         fprintf(stderr,
2315                 "Fatal: unable to initialise SFTP: %s\n", fxp_error());
2316         return 1;                      /* failure */
2317     }
2318
2319     /*
2320      * Find out where our home directory is.
2321      */
2322     sftp_register(req = fxp_realpath_send("."));
2323     rreq = sftp_find_request(pktin = sftp_recv());
2324     assert(rreq == req);
2325     homedir = fxp_realpath_recv(pktin, rreq);
2326
2327     if (!homedir) {
2328         fprintf(stderr,
2329                 "Warning: failed to resolve home directory: %s\n",
2330                 fxp_error());
2331         homedir = dupstr(".");
2332     } else {
2333         printf("Remote working directory is %s\n", homedir);
2334     }
2335     pwd = dupstr(homedir);
2336     return 0;
2337 }
2338
2339 void do_sftp_cleanup()
2340 {
2341     char ch;
2342     if (back) {
2343         back->special(backhandle, TS_EOF);
2344         sftp_recvdata(&ch, 1);
2345         back->free(backhandle);
2346         sftp_cleanup_request();
2347         back = NULL;
2348         backhandle = NULL;
2349     }
2350     if (pwd) {
2351         sfree(pwd);
2352         pwd = NULL;
2353     }
2354     if (homedir) {
2355         sfree(homedir);
2356         homedir = NULL;
2357     }
2358 }
2359
2360 void do_sftp(int mode, int modeflags, char *batchfile)
2361 {
2362     FILE *fp;
2363     int ret;
2364
2365     /*
2366      * Batch mode?
2367      */
2368     if (mode == 0) {
2369
2370         /* ------------------------------------------------------------------
2371          * Now we're ready to do Real Stuff.
2372          */
2373         while (1) {
2374             struct sftp_command *cmd;
2375             cmd = sftp_getcmd(NULL, 0, 0);
2376             if (!cmd)
2377                 break;
2378             ret = cmd->obey(cmd);
2379             if (cmd->words) {
2380                 int i;
2381                 for(i = 0; i < cmd->nwords; i++)
2382                     sfree(cmd->words[i]);
2383                 sfree(cmd->words);
2384             }
2385             sfree(cmd);
2386             if (ret < 0)
2387                 break;
2388         }
2389     } else {
2390         fp = fopen(batchfile, "r");
2391         if (!fp) {
2392             printf("Fatal: unable to open %s\n", batchfile);
2393             return;
2394         }
2395         while (1) {
2396             struct sftp_command *cmd;
2397             cmd = sftp_getcmd(fp, mode, modeflags);
2398             if (!cmd)
2399                 break;
2400             ret = cmd->obey(cmd);
2401             if (ret < 0)
2402                 break;
2403             if (ret == 0) {
2404                 if (!(modeflags & 2))
2405                     break;
2406             }
2407         }
2408         fclose(fp);
2409
2410     }
2411 }
2412
2413 /* ----------------------------------------------------------------------
2414  * Dirty bits: integration with PuTTY.
2415  */
2416
2417 static int verbose = 0;
2418
2419 /*
2420  *  Print an error message and perform a fatal exit.
2421  */
2422 void fatalbox(char *fmt, ...)
2423 {
2424     char *str, *str2;
2425     va_list ap;
2426     va_start(ap, fmt);
2427     str = dupvprintf(fmt, ap);
2428     str2 = dupcat("Fatal: ", str, "\n", NULL);
2429     sfree(str);
2430     va_end(ap);
2431     fputs(str2, stderr);
2432     sfree(str2);
2433
2434     cleanup_exit(1);
2435 }
2436 void modalfatalbox(char *fmt, ...)
2437 {
2438     char *str, *str2;
2439     va_list ap;
2440     va_start(ap, fmt);
2441     str = dupvprintf(fmt, ap);
2442     str2 = dupcat("Fatal: ", str, "\n", NULL);
2443     sfree(str);
2444     va_end(ap);
2445     fputs(str2, stderr);
2446     sfree(str2);
2447
2448     cleanup_exit(1);
2449 }
2450 void connection_fatal(void *frontend, char *fmt, ...)
2451 {
2452     char *str, *str2;
2453     va_list ap;
2454     va_start(ap, fmt);
2455     str = dupvprintf(fmt, ap);
2456     str2 = dupcat("Fatal: ", str, "\n", NULL);
2457     sfree(str);
2458     va_end(ap);
2459     fputs(str2, stderr);
2460     sfree(str2);
2461
2462     cleanup_exit(1);
2463 }
2464
2465 void ldisc_send(void *handle, char *buf, int len, int interactive)
2466 {
2467     /*
2468      * This is only here because of the calls to ldisc_send(NULL,
2469      * 0) in ssh.c. Nothing in PSFTP actually needs to use the
2470      * ldisc as an ldisc. So if we get called with any real data, I
2471      * want to know about it.
2472      */
2473     assert(len == 0);
2474 }
2475
2476 /*
2477  * In psftp, all agent requests should be synchronous, so this is a
2478  * never-called stub.
2479  */
2480 void agent_schedule_callback(void (*callback)(void *, void *, int),
2481                              void *callback_ctx, void *data, int len)
2482 {
2483     assert(!"We shouldn't be here");
2484 }
2485
2486 /*
2487  * Receive a block of data from the SSH link. Block until all data
2488  * is available.
2489  *
2490  * To do this, we repeatedly call the SSH protocol module, with our
2491  * own trap in from_backend() to catch the data that comes back. We
2492  * do this until we have enough data.
2493  */
2494
2495 static unsigned char *outptr;          /* where to put the data */
2496 static unsigned outlen;                /* how much data required */
2497 static unsigned char *pending = NULL;  /* any spare data */
2498 static unsigned pendlen = 0, pendsize = 0;      /* length and phys. size of buffer */
2499 int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
2500 {
2501     unsigned char *p = (unsigned char *) data;
2502     unsigned len = (unsigned) datalen;
2503
2504     /*
2505      * stderr data is just spouted to local stderr and otherwise
2506      * ignored.
2507      */
2508     if (is_stderr) {
2509         if (len > 0)
2510             fwrite(data, 1, len, stderr);
2511         return 0;
2512     }
2513
2514     /*
2515      * If this is before the real session begins, just return.
2516      */
2517     if (!outptr)
2518         return 0;
2519
2520     if ((outlen > 0) && (len > 0)) {
2521         unsigned used = outlen;
2522         if (used > len)
2523             used = len;
2524         memcpy(outptr, p, used);
2525         outptr += used;
2526         outlen -= used;
2527         p += used;
2528         len -= used;
2529     }
2530
2531     if (len > 0) {
2532         if (pendsize < pendlen + len) {
2533             pendsize = pendlen + len + 4096;
2534             pending = sresize(pending, pendsize, unsigned char);
2535         }
2536         memcpy(pending + pendlen, p, len);
2537         pendlen += len;
2538     }
2539
2540     return 0;
2541 }
2542 int from_backend_untrusted(void *frontend_handle, const char *data, int len)
2543 {
2544     /*
2545      * No "untrusted" output should get here (the way the code is
2546      * currently, it's all diverted by FLAG_STDERR).
2547      */
2548     assert(!"Unexpected call to from_backend_untrusted()");
2549     return 0; /* not reached */
2550 }
2551 int sftp_recvdata(char *buf, int len)
2552 {
2553     outptr = (unsigned char *) buf;
2554     outlen = len;
2555
2556     /*
2557      * See if the pending-input block contains some of what we
2558      * need.
2559      */
2560     if (pendlen > 0) {
2561         unsigned pendused = pendlen;
2562         if (pendused > outlen)
2563             pendused = outlen;
2564         memcpy(outptr, pending, pendused);
2565         memmove(pending, pending + pendused, pendlen - pendused);
2566         outptr += pendused;
2567         outlen -= pendused;
2568         pendlen -= pendused;
2569         if (pendlen == 0) {
2570             pendsize = 0;
2571             sfree(pending);
2572             pending = NULL;
2573         }
2574         if (outlen == 0)
2575             return 1;
2576     }
2577
2578     while (outlen > 0) {
2579         if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0)
2580             return 0;                  /* doom */
2581     }
2582
2583     return 1;
2584 }
2585 int sftp_senddata(char *buf, int len)
2586 {
2587     back->send(backhandle, buf, len);
2588     return 1;
2589 }
2590
2591 /*
2592  *  Short description of parameters.
2593  */
2594 static void usage(void)
2595 {
2596     printf("PuTTY Secure File Transfer (SFTP) client\n");
2597     printf("%s\n", ver);
2598     printf("Usage: psftp [options] [user@]host\n");
2599     printf("Options:\n");
2600     printf("  -V        print version information and exit\n");
2601     printf("  -pgpfp    print PGP key fingerprints and exit\n");
2602     printf("  -b file   use specified batchfile\n");
2603     printf("  -bc       output batchfile commands\n");
2604     printf("  -be       don't stop batchfile processing if errors\n");
2605     printf("  -v        show verbose messages\n");
2606     printf("  -load sessname  Load settings from saved session\n");
2607     printf("  -l user   connect with specified username\n");
2608     printf("  -P port   connect to specified port\n");
2609     printf("  -pw passw login with specified password\n");
2610     printf("  -1 -2     force use of particular SSH protocol version\n");
2611     printf("  -4 -6     force use of IPv4 or IPv6\n");
2612     printf("  -C        enable compression\n");
2613     printf("  -i key    private key file for authentication\n");
2614     printf("  -noagent  disable use of Pageant\n");
2615     printf("  -agent    enable use of Pageant\n");
2616     printf("  -batch    disable all interactive prompts\n");
2617     cleanup_exit(1);
2618 }
2619
2620 static void version(void)
2621 {
2622   printf("psftp: %s\n", ver);
2623   cleanup_exit(1);
2624 }
2625
2626 /*
2627  * Connect to a host.
2628  */
2629 static int psftp_connect(char *userhost, char *user, int portnumber)
2630 {
2631     char *host, *realhost;
2632     const char *err;
2633     void *logctx;
2634
2635     /* Separate host and username */
2636     host = userhost;
2637     host = strrchr(host, '@');
2638     if (host == NULL) {
2639         host = userhost;
2640     } else {
2641         *host++ = '\0';
2642         if (user) {
2643             printf("psftp: multiple usernames specified; using \"%s\"\n",
2644                    user);
2645         } else
2646             user = userhost;
2647     }
2648
2649     /*
2650      * If we haven't loaded session details already (e.g., from -load),
2651      * try looking for a session called "host".
2652      */
2653     if (!loaded_session) {
2654         /* Try to load settings for `host' into a temporary config */
2655         Config cfg2;
2656         cfg2.host[0] = '\0';
2657         do_defaults(host, &cfg2);
2658         if (cfg2.host[0] != '\0') {
2659             /* Settings present and include hostname */
2660             /* Re-load data into the real config. */
2661             do_defaults(host, &cfg);
2662         } else {
2663             /* Session doesn't exist or mention a hostname. */
2664             /* Use `host' as a bare hostname. */
2665             strncpy(cfg.host, host, sizeof(cfg.host) - 1);
2666             cfg.host[sizeof(cfg.host) - 1] = '\0';
2667         }
2668     } else {
2669         /* Patch in hostname `host' to session details. */
2670         strncpy(cfg.host, host, sizeof(cfg.host) - 1);
2671         cfg.host[sizeof(cfg.host) - 1] = '\0';
2672     }
2673
2674     /*
2675      * Force use of SSH. (If they got the protocol wrong we assume the
2676      * port is useless too.)
2677      */
2678     if (cfg.protocol != PROT_SSH) {
2679         cfg.protocol = PROT_SSH;
2680         cfg.port = 22;
2681     }
2682
2683     /*
2684      * If saved session / Default Settings says SSH-1 (`1 only' or `1'),
2685      * then change it to SSH-2, on the grounds that that's more likely to
2686      * work for SFTP. (Can be overridden with `-1' option.)
2687      * But if it says `2 only' or `2', respect which.
2688      */
2689     if (cfg.sshprot != 2 && cfg.sshprot != 3)
2690         cfg.sshprot = 2;
2691
2692     /*
2693      * Enact command-line overrides.
2694      */
2695     cmdline_run_saved(&cfg);
2696
2697     /*
2698      * Trim leading whitespace off the hostname if it's there.
2699      */
2700     {
2701         int space = strspn(cfg.host, " \t");
2702         memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
2703     }
2704
2705     /* See if host is of the form user@host */
2706     if (cfg.host[0] != '\0') {
2707         char *atsign = strrchr(cfg.host, '@');
2708         /* Make sure we're not overflowing the user field */
2709         if (atsign) {
2710             if (atsign - cfg.host < sizeof cfg.username) {
2711                 strncpy(cfg.username, cfg.host, atsign - cfg.host);
2712                 cfg.username[atsign - cfg.host] = '\0';
2713             }
2714             memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
2715         }
2716     }
2717
2718     /*
2719      * Trim a colon suffix off the hostname if it's there.
2720      */
2721     cfg.host[strcspn(cfg.host, ":")] = '\0';
2722
2723     /*
2724      * Remove any remaining whitespace from the hostname.
2725      */
2726     {
2727         int p1 = 0, p2 = 0;
2728         while (cfg.host[p2] != '\0') {
2729             if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
2730                 cfg.host[p1] = cfg.host[p2];
2731                 p1++;
2732             }
2733             p2++;
2734         }
2735         cfg.host[p1] = '\0';
2736     }
2737
2738     /* Set username */
2739     if (user != NULL && user[0] != '\0') {
2740         strncpy(cfg.username, user, sizeof(cfg.username) - 1);
2741         cfg.username[sizeof(cfg.username) - 1] = '\0';
2742     }
2743
2744     if (portnumber)
2745         cfg.port = portnumber;
2746
2747     /*
2748      * Disable scary things which shouldn't be enabled for simple
2749      * things like SCP and SFTP: agent forwarding, port forwarding,
2750      * X forwarding.
2751      */
2752     cfg.x11_forward = 0;
2753     cfg.agentfwd = 0;
2754     cfg.portfwd[0] = cfg.portfwd[1] = '\0';
2755     cfg.ssh_simple = TRUE;
2756
2757     /* Set up subsystem name. */
2758     strcpy(cfg.remote_cmd, "sftp");
2759     cfg.ssh_subsys = TRUE;
2760     cfg.nopty = TRUE;
2761
2762     /*
2763      * Set up fallback option, for SSH-1 servers or servers with the
2764      * sftp subsystem not enabled but the server binary installed
2765      * in the usual place. We only support fallback on Unix
2766      * systems, and we use a kludgy piece of shellery which should
2767      * try to find sftp-server in various places (the obvious
2768      * systemwide spots /usr/lib and /usr/local/lib, and then the
2769      * user's PATH) and finally give up.
2770      * 
2771      *   test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server
2772      *   test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server
2773      *   exec sftp-server
2774      * 
2775      * the idea being that this will attempt to use either of the
2776      * obvious pathnames and then give up, and when it does give up
2777      * it will print the preferred pathname in the error messages.
2778      */
2779     cfg.remote_cmd_ptr2 =
2780         "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
2781         "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
2782         "exec sftp-server";
2783     cfg.ssh_subsys2 = FALSE;
2784
2785     back = &ssh_backend;
2786
2787     err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,
2788                      0, cfg.tcp_keepalives);
2789     if (err != NULL) {
2790         fprintf(stderr, "ssh_init: %s\n", err);
2791         return 1;
2792     }
2793     logctx = log_init(NULL, &cfg);
2794     back->provide_logctx(backhandle, logctx);
2795     console_provide_logctx(logctx);
2796     while (!back->sendok(backhandle)) {
2797         if (ssh_sftp_loop_iteration() < 0) {
2798             fprintf(stderr, "ssh_init: error during SSH connection setup\n");
2799             return 1;
2800         }
2801     }
2802     if (verbose && realhost != NULL)
2803         printf("Connected to %s\n", realhost);
2804     if (realhost != NULL)
2805         sfree(realhost);
2806     return 0;
2807 }
2808
2809 void cmdline_error(char *p, ...)
2810 {
2811     va_list ap;
2812     fprintf(stderr, "psftp: ");
2813     va_start(ap, p);
2814     vfprintf(stderr, p, ap);
2815     va_end(ap);
2816     fprintf(stderr, "\n       try typing \"psftp -h\" for help\n");
2817     exit(1);
2818 }
2819
2820 /*
2821  * Main program. Parse arguments etc.
2822  */
2823 int psftp_main(int argc, char *argv[])
2824 {
2825     int i;
2826     int portnumber = 0;
2827     char *userhost, *user;
2828     int mode = 0;
2829     int modeflags = 0;
2830     char *batchfile = NULL;
2831     int errors = 0;
2832
2833     flags = FLAG_STDERR | FLAG_INTERACTIVE
2834 #ifdef FLAG_SYNCAGENT
2835         | FLAG_SYNCAGENT
2836 #endif
2837         ;
2838     cmdline_tooltype = TOOLTYPE_FILETRANSFER;
2839     sk_init();
2840
2841     userhost = user = NULL;
2842
2843     /* Load Default Settings before doing anything else. */
2844     do_defaults(NULL, &cfg);
2845     loaded_session = FALSE;
2846
2847     errors = 0;
2848     for (i = 1; i < argc; i++) {
2849         int ret;
2850         if (argv[i][0] != '-') {
2851             if (userhost)
2852                 usage();
2853             else
2854                 userhost = dupstr(argv[i]);
2855             continue;
2856         }
2857         ret = cmdline_process_param(argv[i], i+1<argc?argv[i+1]:NULL, 1, &cfg);
2858         if (ret == -2) {
2859             cmdline_error("option \"%s\" requires an argument", argv[i]);
2860         } else if (ret == 2) {
2861             i++;               /* skip next argument */
2862         } else if (ret == 1) {
2863             /* We have our own verbosity in addition to `flags'. */
2864             if (flags & FLAG_VERBOSE)
2865                 verbose = 1;
2866         } else if (strcmp(argv[i], "-h") == 0 ||
2867                    strcmp(argv[i], "-?") == 0) {
2868             usage();
2869         } else if (strcmp(argv[i], "-pgpfp") == 0) {
2870             pgp_fingerprints();
2871             return 1;
2872         } else if (strcmp(argv[i], "-V") == 0) {
2873             version();
2874         } else if (strcmp(argv[i], "-batch") == 0) {
2875             console_batch_mode = 1;
2876         } else if (strcmp(argv[i], "-b") == 0 && i + 1 < argc) {
2877             mode = 1;
2878             batchfile = argv[++i];
2879         } else if (strcmp(argv[i], "-bc") == 0) {
2880             modeflags = modeflags | 1;
2881         } else if (strcmp(argv[i], "-be") == 0) {
2882             modeflags = modeflags | 2;
2883         } else if (strcmp(argv[i], "--") == 0) {
2884             i++;
2885             break;
2886         } else {
2887             cmdline_error("unknown option \"%s\"", argv[i]);
2888         }
2889     }
2890     argc -= i;
2891     argv += i;
2892     back = NULL;
2893
2894     /*
2895      * If the loaded session provides a hostname, and a hostname has not
2896      * otherwise been specified, pop it in `userhost' so that
2897      * `psftp -load sessname' is sufficient to start a session.
2898      */
2899     if (!userhost && cfg.host[0] != '\0') {
2900         userhost = dupstr(cfg.host);
2901     }
2902
2903     /*
2904      * If a user@host string has already been provided, connect to
2905      * it now.
2906      */
2907     if (userhost) {
2908         int ret;
2909         ret = psftp_connect(userhost, user, portnumber);
2910         sfree(userhost);
2911         if (ret)
2912             return 1;
2913         if (do_sftp_init())
2914             return 1;
2915     } else {
2916         printf("psftp: no hostname specified; use \"open host.name\""
2917                " to connect\n");
2918     }
2919
2920     do_sftp(mode, modeflags, batchfile);
2921
2922     if (back != NULL && back->connected(backhandle)) {
2923         char ch;
2924         back->special(backhandle, TS_EOF);
2925         sftp_recvdata(&ch, 1);
2926     }
2927     do_sftp_cleanup();
2928     random_save_seed();
2929     cmdline_cleanup();
2930     console_provide_logctx(NULL);
2931     sk_cleanup();
2932
2933     return 0;
2934 }