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