Skip to content
Snippets Groups Projects
pscp.c 71.7 KiB
Newer Older
 * scp.c  -  Scp (Secure Copy) client for PuTTY.
 * Joris van Rantwijk, Simon Tatham
 * This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen.
 * They, in turn, used stuff from BSD rcp.
 * (SGT, 2001-09-10: Joris van Rantwijk assures me that although
 * this file as originally submitted was inspired by, and
 * _structurally_ based on, ssh-1.2.26's scp.c, there wasn't any
 * actual code duplicated, so the above comment shouldn't give rise
 * to licensing issues.)
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#include "putty.h"
#include "ssh.h"
#include "sftp.h"
static bool list = false;
static bool verbose = false;
static bool recursive = false;
static bool preserve = false;
static bool targetshouldbedirectory = false;
static bool statistics = true;
static int prev_stats_len = 0;
static bool scp_unsafe_mode = false;
static int errs = 0;
static bool try_scp = true;
static bool try_sftp = true;
static bool main_cmd_is_sftp = false;
static bool fallback_cmd_is_sftp = false;
static bool using_sftp = false;
static bool uploading = false;
static Backend *backend;
static Conf *conf;
static bool sent_eof = false;
static void source(const char *src);
static void rsource(const char *src);
static void sink(const char *targ, const char *src);
const char *const appname = "PSCP";

/*
 * The maximum amount of queued data we accept before we stop and
 * wait for the server to process some.
 */
#define MAX_SCP_BUFSIZE 16384

void ldisc_echoedit_update(Ldisc *ldisc) { }
static size_t pscp_output(Seat *, bool is_stderr, const void *, size_t);
static bool pscp_eof(Seat *);

static const SeatVtable pscp_seat_vt = {
    .output = pscp_output,
    .eof = pscp_eof,
    .get_userpass_input = filexfer_get_userpass_input,
    .notify_remote_exit = nullseat_notify_remote_exit,
    .connection_fatal = console_connection_fatal,
    .update_specials_menu = nullseat_update_specials_menu,
    .get_ttymode = nullseat_get_ttymode,
    .set_busy_status = nullseat_set_busy_status,
    .verify_ssh_host_key = console_verify_ssh_host_key,
    .confirm_weak_crypto_primitive = console_confirm_weak_crypto_primitive,
    .confirm_weak_cached_hostkey = console_confirm_weak_cached_hostkey,
    .is_utf8 = nullseat_is_never_utf8,
    .echoedit_update = nullseat_echoedit_update,
    .get_x_display = nullseat_get_x_display,
    .get_windowid = nullseat_get_windowid,
    .get_window_pixel_size = nullseat_get_window_pixel_size,
    .stripctrl_new = console_stripctrl_new,
    .set_trust_status = nullseat_set_trust_status_vacuously,
    .verbose = cmdline_seat_verbose,
    .interactive = nullseat_interactive_no,
    .get_cursor_position = nullseat_get_cursor_position,
};
static Seat pscp_seat[1] = {{ &pscp_seat_vt }};

static void tell_char(FILE *stream, char c)
static void tell_str(FILE *stream, const char *str)
    for (i = 0; i < strlen(str); ++i)
        tell_char(stream, str[i]);
static void abandon_stats(void)
{
    /*
     * Output a \n to stdout (which is where we've been sending
     * transfer statistics) so that the cursor will move to the next
     * line. We should do this before displaying any other kind of
     * output like an error message.
    if (prev_stats_len) {
        putchar('\n');
        fflush(stdout);
        prev_stats_len = 0;
static PRINTF_LIKE(2, 3) void tell_user(FILE *stream, const char *fmt, ...)
    va_list ap;
    va_start(ap, fmt);
    str2 = dupcat(str, "\n");
    tell_str(stream, str2);
    sfree(str2);
/*
 * Receive a block of data from the SSH link. Block until all data
 * is available.
 *
 * To do this, we repeatedly call the SSH protocol module, with our
 * own pscp_output() function to catch the data that comes back. We do
 * this until we have enough data.
static bufchain received_data;
static BinarySink *stderr_bs;
static size_t pscp_output(
    Seat *seat, bool is_stderr, const void *data, size_t len)
     * stderr data is just spouted to local stderr (optionally via a
     * sanitiser) and otherwise ignored.
        put_data(stderr_bs, data, len);
    bufchain_add(&received_data, data, len);
static bool pscp_eof(Seat *seat)
     * We usually expect to be the party deciding when to close the
     * connection, so if we see EOF before we sent it ourselves, we
     * should panic. The exception is if we're using old-style scp and
     * downloading rather than uploading.
    if ((using_sftp || uploading) && !sent_eof) {
        seat_connection_fatal(
            pscp_seat, "Received unexpected end-of-file from server");
    return false;
static bool ssh_scp_recv(void *vbuf, size_t len)
    char *buf = (char *)vbuf;
    while (len > 0) {
        while (bufchain_size(&received_data) == 0) {
            if (backend_exitcode(backend) >= 0 ||
                ssh_sftp_loop_iteration() < 0)
                return false;          /* doom */
        }
        size_t got = bufchain_fetch_consume_up_to(&received_data, buf, len);
        buf += got;
        len -= got;
}

/*
 * Loop through the ssh connection and authentication process.
 */
    while (!backend_sendok(backend)) {
        if (backend_exitcode(backend) >= 0) {
        if (ssh_sftp_loop_iteration() < 0) {
            return;                    /* doom */

    /* Work out which backend we ended up using. */
    if (!ssh_fallback_cmd(backend))
        using_sftp = main_cmd_is_sftp;
        using_sftp = fallback_cmd_is_sftp;
    if (verbose) {
        if (using_sftp)
            tell_user(stderr, "Using SFTP");
        else
            tell_user(stderr, "Using SCP1");
/*
 *  Print an error message and exit after closing the SSH link.
 */
static NORETURN PRINTF_LIKE(1, 2) void bump(const char *fmt, ...)
    str2 = dupcat(str, "\n");
    tell_str(stderr, str2);
    sfree(str2);
    if (backend && backend_connected(backend)) {
        backend_special(backend, SS_EOF, 0);
        sent_eof = true;
        ssh_scp_recv(&ch, 1);
/*
 * A nasty loop macro that lets me get an escape-sequence sanitised
 * version of a string for display, and free it automatically
 * afterwards.
 */
static StripCtrlChars *string_scc;
#define with_stripctrl(varname, input)                                  \
    for (char *varname = stripctrl_string(string_scc, input); varname;  \
         sfree(varname), varname = NULL)

/*
 * Wait for the reply to a single SFTP request. Parallels the same
 * function in psftp.c (but isn't centralised into sftp.c because the
 * latter module handles SFTP only and shouldn't assume that SFTP is
 * the only thing going on by calling seat_connection_fatal).
 */
struct sftp_packet *sftp_wait_for_reply(struct sftp_request *req)
{
    struct sftp_packet *pktin;
    struct sftp_request *rreq;

    sftp_register(req);
    pktin = sftp_recv();
    if (pktin == NULL) {
        seat_connection_fatal(
            pscp_seat, "did not receive SFTP response packet from server");
    }
    rreq = sftp_find_request(pktin);
    if (rreq != req) {
        seat_connection_fatal(
            pscp_seat,
            "unable to understand SFTP response packet from server: %s",
            fxp_error());
    }
/*
 *  Open an SSH connection to user@host and execute cmd.
 */
static void do_cmd(char *host, char *user, char *cmd)
{

    if (host == NULL || host[0] == '\0')
        bump("Empty host name");
    host[host_strcspn(host, ":")] = '\0';
    /*
     * If we haven't loaded session details already (e.g., from -load),
     * try looking for a session called "host".
     */
    if (!cmdline_loaded_session()) {
        /* Try to load settings for `host' into a temporary config */
        Conf *conf2 = conf_new();
        conf_set_str(conf2, CONF_host, "");
        do_defaults(host, conf2);
        if (conf_get_str(conf2, CONF_host)[0] != '\0') {
            /* Settings present and include hostname */
            /* Re-load data into the real config. */
            do_defaults(host, conf);
        } else {
            /* Session doesn't exist or mention a hostname. */
            /* Use `host' as a bare hostname. */
            conf_set_str(conf, CONF_host, host);
        }
        /* Patch in hostname `host' to session details. */
        conf_set_str(conf, CONF_host, host);
     * Force protocol to SSH if the user has somehow contrived to
     * select one we don't support (e.g. by loading an inappropriate
     * saved session). In that situation we assume the port number is
     * useless too.)
    if (!backend_vt_from_proto(conf_get_int(conf, CONF_protocol))) {
        conf_set_int(conf, CONF_protocol, PROT_SSH);
        conf_set_int(conf, CONF_port, 22);
     * Muck about with the hostname in various ways.
        char *hostbuf = dupstr(conf_get_str(conf, CONF_host));
        char *host = hostbuf;
        char *p, *q;

        /*
         * Trim leading whitespace.
         */
        host += strspn(host, " \t");

        /*
         * See if host is of the form user@host, and separate out
         * the username if so.
         */
        if (host[0] != '\0') {
            char *atsign = strrchr(host, '@');
            if (atsign) {
                *atsign = '\0';
                conf_set_str(conf, CONF_username, host);
                host = atsign + 1;
            }
        }

        /*
         * Remove any remaining whitespace.
         */
        p = hostbuf;
        q = host;
        while (*q) {
            if (*q != ' ' && *q != '\t')
                *p++ = *q;
            q++;
        }
        *p = '\0';

        conf_set_str(conf, CONF_host, hostbuf);
        sfree(hostbuf);
    /* Set username */
    if (user != NULL && user[0] != '\0') {
        conf_set_str(conf, CONF_username, user);
    } else if (conf_get_str(conf, CONF_username)[0] == '\0') {
        user = get_username();
        if (!user)
            bump("Empty user name");
        else {
            if (verbose)
                tell_user(stderr, "Guessing user name: %s", user);
            conf_set_str(conf, CONF_username, user);
            sfree(user);
        }
    /*
     * Force protocol to SSH if the user has somehow contrived to
     * select one we don't support (e.g. by loading an inappropriate
     * saved session). In that situation we assume the port number is
     * useless too.)
     */
    if (!backend_vt_from_proto(conf_get_int(conf, CONF_protocol))) {
        conf_set_int(conf, CONF_protocol, PROT_SSH);
        conf_set_int(conf, CONF_port, 22);
    }

    /*
     * Disable scary things which shouldn't be enabled for simple
     * things like SCP and SFTP: agent forwarding, port forwarding,
     * X forwarding.
     */
    conf_set_bool(conf, CONF_x11_forward, false);
    conf_set_bool(conf, CONF_agentfwd, false);
    conf_set_bool(conf, CONF_ssh_simple, true);
        char *key;
        while ((key = conf_get_str_nthstrkey(conf, CONF_portfwd, 0)) != NULL)
            conf_del_str_str(conf, CONF_portfwd, key);
     * Set up main and possibly fallback command depending on
     * options specified by user.
     * Attempt to start the SFTP subsystem as a first choice,
     * falling back to the provided scp command if that fails.
     */
    conf_set_str(conf, CONF_remote_cmd2, "");
    if (try_sftp) {
        /* First choice is SFTP subsystem. */
        main_cmd_is_sftp = true;
        conf_set_str(conf, CONF_remote_cmd, "sftp");
        conf_set_bool(conf, CONF_ssh_subsys, true);
        if (try_scp) {
            /* Fallback is to use the provided scp command. */
            fallback_cmd_is_sftp = false;
            conf_set_str(conf, CONF_remote_cmd2, cmd);
            conf_set_bool(conf, CONF_ssh_subsys2, false);
        } else {
            /* Since we're not going to try SCP, we may as well try
             * harder to find an SFTP server, since in the current
             * implementation we have a spare slot. */
            fallback_cmd_is_sftp = true;
            /* see psftp.c for full explanation of this kludge */
            conf_set_str(conf, CONF_remote_cmd2,
                         "test -x /usr/lib/sftp-server &&"
                         " exec /usr/lib/sftp-server\n"
                         "test -x /usr/local/lib/sftp-server &&"
                         " exec /usr/local/lib/sftp-server\n"
                         "exec sftp-server");
            conf_set_bool(conf, CONF_ssh_subsys2, false);
        }
    } else {
        /* Don't try SFTP at all; just try the scp command. */
        main_cmd_is_sftp = false;
        conf_set_str(conf, CONF_remote_cmd, cmd);
        conf_set_bool(conf, CONF_ssh_subsys, false);
    conf_set_bool(conf, CONF_nopty, true);
Simon Tatham's avatar
Simon Tatham committed
    logctx = log_init(console_cli_logpolicy, conf);
Simon Tatham's avatar
Simon Tatham committed
    platform_psftp_pre_conn_setup(console_cli_logpolicy);
    err = backend_init(backend_vt_from_proto(
                           conf_get_int(conf, CONF_protocol)),
                       pscp_seat, &backend, logctx, conf,
                       conf_get_str(conf, CONF_host),
                       conf_get_int(conf, CONF_port),
                       &realhost, 0,
                       conf_get_bool(conf, CONF_tcp_keepalives));
        bump("ssh_init: %s", err);
    if (verbose && realhost != NULL && errs == 0)
        tell_user(stderr, "Connected to %s", realhost);
}

/*
 *  Update statistic information about current file.
 */
static void print_stats(const char *name, uint64_t size, uint64_t done,
                        time_t start, time_t now)
    float ratebs;
    unsigned long eta;
    elap = (unsigned long) difftime(now, start);
        ratebs = (float)done / elap;
        ratebs = (float)done;
        eta = size - done;
    else
        eta = (unsigned long)((size - done) / ratebs);
    etastr = dupprintf("%02ld:%02ld:%02ld",
                       eta / 3600, (eta % 3600) / 60, eta % 60);
    pct = (int) (100.0 * done / size);
        /* divide by 1024 to provide kB */
        len = printf("\r%-25.25s | %"PRIu64" kB | %5.1f kB/s | "
                     "ETA: %8s | %3d%%", name, done >> 10,
                     ratebs / 1024.0, etastr, pct);
        if (len < prev_stats_len)
            printf("%*s", prev_stats_len - len, "");
        prev_stats_len = len;
}

/*
 *  Find a colon in str and return a pointer to the colon.
 *  This is used to separate hostname from filename.
static char *colon(char *str)
    /* We ignore a leading colon, since the hostname cannot be
       empty. We also ignore a colon as second character because
       of filenames like f:myfile.txt. */
    if (str[0] == '\0' || str[0] == ':' ||
        (str[0] != '[' && str[1] == ':'))
    str += host_strcspn(str, ":/\\");
/*
 * Determine whether a string is entirely composed of dots.
 */
static bool is_dots(char *str)
/*
 *  Wait for a response from the other side.
 *  Return 0 if ok, -1 if error.
 */
static int response(void)
{
    if (!ssh_scp_recv(&resp, 1))
        bump("Lost connection");
      case 0:                          /* ok */
        return (0);
        rbuf[p++] = resp;
        /* fallthrough */
      case 1:                          /* error */
      case 2:                          /* fatal error */
        do {
            if (!ssh_scp_recv(&ch, 1))
                bump("Protocol error: Lost connection");
            rbuf[p++] = ch;
        } while (p < sizeof(rbuf) && ch != '\n');
        rbuf[p - 1] = '\0';
        if (resp == 1)
            tell_user(stderr, "%s", rbuf);
        else
            bump("%s", rbuf);
        errs++;
        return (-1);
bool sftp_recvdata(char *buf, size_t len)
    return ssh_scp_recv(buf, len);
bool sftp_senddata(const char *buf, size_t len)
    backend_send(backend, buf, len);
size_t sftp_sendbuffer(void)
    return backend_sendbuffer(backend);

/* ----------------------------------------------------------------------
 * sftp-based replacement for the hacky `pscp -ls'.
 */
void list_directory_from_sftp_warn_unsorted(void)
    fprintf(stderr,
            "Directory is too large to sort; writing file names unsorted\n");

void list_directory_from_sftp_print(struct fxp_name *name)
{
    with_stripctrl(san, name->longname)
        printf("%s\n", san);
}

void scp_sftp_listdir(const char *dirname)
{
    struct fxp_handle *dirh;
    struct fxp_names *names;
        tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
        errs++;
        return;
    printf("Listing directory %s\n", dirname);

    req = fxp_opendir_send(dirname);
    pktin = sftp_wait_for_reply(req);
    dirh = fxp_opendir_recv(pktin, req);
                tell_user(stderr, "Unable to open %s: %s\n", dirname, fxp_error());
                errs++;
        struct list_directory_from_sftp_ctx *ctx =
            list_directory_from_sftp_new();
            req = fxp_readdir_send(dirh);
            pktin = sftp_wait_for_reply(req);
            names = fxp_readdir_recv(pktin, req);

            if (names == NULL) {
                if (fxp_error_type() == SSH_FX_EOF)
                    break;
                printf("Reading directory %s: %s\n", dirname, fxp_error());
                break;
            }
            if (names->nnames == 0) {
                fxp_free_names(names);
                break;
            }

            for (size_t i = 0; i < names->nnames; i++)
                list_directory_from_sftp_feed(ctx, &names->names[i]);
            fxp_free_names(names);
        }
        req = fxp_close_send(dirh);
        pktin = sftp_wait_for_reply(req);
        fxp_close_recv(pktin, req);
        list_directory_from_sftp_finish(ctx);
        list_directory_from_sftp_free(ctx);
/* ----------------------------------------------------------------------
 * Helper routines that contain the actual SCP protocol elements,
 * implemented both as SCP1 and SFTP.
static struct scp_sftp_dirstack {
    struct scp_sftp_dirstack *next;
    struct fxp_name *names;
    int namepos, namelen;
    char *dirpath;
    bool matched_something;     /* wildcard match set was non-empty */
} *scp_sftp_dirstack_head;
static char *scp_sftp_remotepath, *scp_sftp_currentname;
static char *scp_sftp_wildcard;
static bool scp_sftp_targetisdir, scp_sftp_donethistarget;
static bool scp_sftp_preserve, scp_sftp_recursive;
static unsigned long scp_sftp_mtime, scp_sftp_atime;
static bool scp_has_times;
static struct fxp_handle *scp_sftp_filehandle;
static struct fxp_xfer *scp_sftp_xfer;
static uint64_t scp_sftp_fileoffset;
int scp_source_setup(const char *target, bool shouldbedir)
        /*
         * Find out whether the target filespec is in fact a
         * directory.
         */
        struct sftp_packet *pktin;
        struct sftp_request *req;
        struct fxp_attrs attrs;
        bool ret;

        if (!fxp_init()) {
            tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
            errs++;
            return 1;
        }

        req = fxp_stat_send(target);
        pktin = sftp_wait_for_reply(req);
        ret = fxp_stat_recv(pktin, req, &attrs);
        if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
            scp_sftp_targetisdir = false;
        else
            scp_sftp_targetisdir = (attrs.permissions & 0040000) != 0;
        if (shouldbedir && !scp_sftp_targetisdir) {
            bump("pscp: remote filespec %s: not a directory\n", target);
        }
        scp_sftp_remotepath = dupstr(target);
        scp_has_times = false;
        (void) response();
        /* do nothing; we never need to send our errors to the server */
        backend_send(backend, "\001", 1);/* scp protocol error prefix */
        backend_send(backend, str, strlen(str));
    return 0;                          /* can't fail */
}

int scp_send_filetimes(unsigned long mtime, unsigned long atime)
{
        scp_sftp_mtime = mtime;
        scp_sftp_atime = atime;
        scp_has_times = true;
        return 0;
        char buf[80];
        sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
        backend_send(backend, buf, strlen(buf));
        return response();
int scp_send_filename(const char *name, uint64_t size, int permissions)
        char *fullname;
        struct sftp_packet *pktin;
        struct sftp_request *req;
        if (scp_sftp_targetisdir) {
            fullname = dupcat(scp_sftp_remotepath, "/", name);
        } else {
            fullname = dupstr(scp_sftp_remotepath);
        }
        attrs.flags = 0;
        PUT_PERMISSIONS(attrs, permissions);

        req = fxp_open_send(fullname,
                            SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC,
                            &attrs);
        pktin = sftp_wait_for_reply(req);
        scp_sftp_filehandle = fxp_open_recv(pktin, req);
        if (!scp_sftp_filehandle) {
            tell_user(stderr, "pscp: unable to open %s: %s",
                      fullname, fxp_error());
            errs++;
            return 1;
        }
        scp_sftp_fileoffset = 0;
        scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle,
                                         scp_sftp_fileoffset);
        sfree(fullname);
        return 0;
        if (permissions < 0)
            permissions = 0644;
        buf = dupprintf("C%04o %"PRIu64" ", (int)(permissions & 07777), size);
        backend_send(backend, buf, strlen(buf));
        backend_send(backend, name, strlen(name));
        backend_send(backend, "\n", 1);
        return response();
}

int scp_send_filedata(char *data, int len)
{
        int ret;
        struct sftp_packet *pktin;

        if (!scp_sftp_filehandle) {
            return 1;
        }

        while (!xfer_upload_ready(scp_sftp_xfer)) {
            if (toplevel_callback_pending()) {
                /* If we have pending callbacks, they might make
                 * xfer_upload_ready start to return true. So we should
                 * run them and then re-check xfer_upload_ready, before
                 * we go as far as waiting for an entire packet to
                 * arrive. */
                run_toplevel_callbacks();
                continue;
            }
            pktin = sftp_recv();
            ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin);
            if (ret <= 0) {
                tell_user(stderr, "error while writing: %s", fxp_error());
                if (ret == INT_MIN)        /* pktin not even freed */
                    sfree(pktin);
        xfer_upload_data(scp_sftp_xfer, data, len);
        scp_sftp_fileoffset += len;
        return 0;
        int bufsize = backend_send(backend, data, len);
        /*
         * If the network transfer is backing up - that is, the
         * remote site is not accepting data as fast as we can
         * produce it - then we must loop on network events until
         * we have space in the buffer again.
         */
        while (bufsize > MAX_SCP_BUFSIZE) {
            if (ssh_sftp_loop_iteration() < 0)
                return 1;
            bufsize = backend_sendbuffer(backend);
        struct fxp_attrs attrs;
        struct sftp_packet *pktin;
        struct sftp_request *req;

        while (!xfer_done(scp_sftp_xfer)) {
            pktin = sftp_recv();
            int ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin);
            if (ret <= 0) {
                tell_user(stderr, "error while writing: %s", fxp_error());
                if (ret == INT_MIN)        /* pktin not even freed */
                    sfree(pktin);
                errs++;
                return 1;
            }
        }
        xfer_cleanup(scp_sftp_xfer);

        if (!scp_sftp_filehandle) {
            return 1;
        }
        if (scp_has_times) {
            attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME;
            attrs.atime = scp_sftp_atime;
            attrs.mtime = scp_sftp_mtime;
            req = fxp_fsetstat_send(scp_sftp_filehandle, attrs);
            pktin = sftp_wait_for_reply(req);
            bool ret = fxp_fsetstat_recv(pktin, req);
            if (!ret) {
                tell_user(stderr, "unable to set file times: %s", fxp_error());
                errs++;
            }
        }
        req = fxp_close_send(scp_sftp_filehandle);
        pktin = sftp_wait_for_reply(req);
        fxp_close_recv(pktin, req);
        scp_has_times = false;
        return 0;
        backend_send(backend, "", 1);
        return response();
char *scp_save_remotepath(void)
{
    if (using_sftp)
        return scp_sftp_remotepath;
void scp_restore_remotepath(char *data)
        scp_sftp_remotepath = data;
int scp_send_dirname(const char *name, int modes)
        char *fullname;
        char const *err;
        struct fxp_attrs attrs;
        struct sftp_packet *pktin;
        struct sftp_request *req;
        if (scp_sftp_targetisdir) {
            fullname = dupcat(scp_sftp_remotepath, "/", name);
        } else {
            fullname = dupstr(scp_sftp_remotepath);
        }

        /*
         * We don't worry about whether we managed to create the
         * directory, because if it exists already it's OK just to
         * use it. Instead, we will stat it afterwards, and if it
         * exists and is a directory we will assume we were either
         * successful or it didn't matter.
         */
        req = fxp_mkdir_send(fullname, NULL);
        pktin = sftp_wait_for_reply(req);
        ret = fxp_mkdir_recv(pktin, req);
        if (!ret)
            err = fxp_error();
        else
            err = "server reported no error";
        req = fxp_stat_send(fullname);
        pktin = sftp_wait_for_reply(req);
        ret = fxp_stat_recv(pktin, req, &attrs);
        if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
            !(attrs.permissions & 0040000)) {
            tell_user(stderr, "unable to create directory %s: %s",
                      fullname, err);
        scp_sftp_remotepath = fullname;
        char buf[40];
        sprintf(buf, "D%04o 0 ", modes);
        backend_send(backend, buf, strlen(buf));
        backend_send(backend, name, strlen(name));
        backend_send(backend, "\n", 1);
        return response();
        sfree(scp_sftp_remotepath);
        return 0;
        backend_send(backend, "E\n", 2);
        return response();
    }
}

/*
 * Yes, I know; I have an scp_sink_setup _and_ an scp_sink_init.
 * That's bad. The difference is that scp_sink_setup is called once