source: filezilla/trunk/fuentes/src/putty/psftp.c @ 3185

Last change on this file since 3185 was 3185, checked in by jrpelegrina, 3 years ago

Update new version: 3.15.02

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