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