+/*
+ * Return a pointer to the portion of str that comes after the last
+ * slash (or backslash or colon, if `local' is TRUE).
+ */
+static char *stripslashes(char *str, int local)
+{
+ char *p;
+
+ if (local) {
+ p = strchr(str, ':');
+ if (p) str = p+1;
+ }
+
+ p = strrchr(str, '/');
+ if (p) str = p+1;
+
+ if (local) {
+ p = strrchr(str, '\\');
+ if (p) str = p+1;
+ }
+
+ return str;
+}
+
+/*
+ * Determine whether a string is entirely composed of dots.
+ */
+static int is_dots(char *str)
+{
+ return str[strspn(str, ".")] == '\0';
+}
+
+/*
+ * Wait for a response from the other side.
+ * Return 0 if ok, -1 if error.
+ */
+static int response(void)
+{
+ char ch, resp, rbuf[2048];
+ int p;
+
+ if (ssh_scp_recv((unsigned char *) &resp, 1) <= 0)
+ bump("Lost connection");
+
+ p = 0;
+ switch (resp) {
+ case 0: /* ok */
+ return (0);
+ default:
+ rbuf[p++] = resp;
+ /* fallthrough */
+ case 1: /* error */
+ case 2: /* fatal error */
+ do {
+ if (ssh_scp_recv((unsigned char *) &ch, 1) <= 0)
+ 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\n", rbuf);
+ else
+ bump("%s", rbuf);
+ errs++;
+ return (-1);
+ }
+}
+
+int sftp_recvdata(char *buf, int len)
+{
+ return ssh_scp_recv((unsigned char *) buf, len);
+}
+int sftp_senddata(char *buf, int len)
+{
+ back->send(backhandle, buf, len);
+ return 1;
+}
+
+/* ----------------------------------------------------------------------
+ * sftp-based replacement for the hacky `pscp -ls'.
+ */
+static int sftp_ls_compare(const void *av, const void *bv)
+{
+ const struct fxp_name *a = (const struct fxp_name *) av;
+ const struct fxp_name *b = (const struct fxp_name *) bv;
+ return strcmp(a->filename, b->filename);
+}
+void scp_sftp_listdir(char *dirname)
+{
+ struct fxp_handle *dirh;
+ struct fxp_names *names;
+ struct fxp_name *ournames;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int nnames, namesize;
+ int i;
+
+ if (!fxp_init()) {
+ tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
+ errs++;
+ return;
+ }
+
+ printf("Listing directory %s\n", dirname);
+
+ sftp_register(req = fxp_opendir_send(dirname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ dirh = fxp_opendir_recv(pktin, rreq);
+
+ if (dirh == NULL) {
+ printf("Unable to open %s: %s\n", dirname, fxp_error());
+ } else {
+ nnames = namesize = 0;
+ ournames = NULL;
+
+ while (1) {
+
+ sftp_register(req = fxp_readdir_send(dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ names = fxp_readdir_recv(pktin, rreq);
+
+ 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;
+ }
+
+ if (nnames + names->nnames >= namesize) {
+ namesize += names->nnames + 128;
+ ournames = sresize(ournames, namesize, struct fxp_name);
+ }
+
+ for (i = 0; i < names->nnames; i++)
+ ournames[nnames++] = names->names[i];
+
+ names->nnames = 0; /* prevent free_names */
+ fxp_free_names(names);
+ }
+ sftp_register(req = fxp_close_send(dirh));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ fxp_close_recv(pktin, rreq);
+
+ /*
+ * Now we have our filenames. Sort them by actual file
+ * name, and then output the longname parts.
+ */
+ qsort(ournames, nnames, sizeof(*ournames), sftp_ls_compare);
+
+ /*
+ * And print them.
+ */
+ for (i = 0; i < nnames; i++)
+ printf("%s\n", ournames[i].longname);
+ }
+}
+
+/* ----------------------------------------------------------------------
+ * 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;
+ char *wildcard;
+ int 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 int scp_sftp_targetisdir, scp_sftp_donethistarget;
+static int scp_sftp_preserve, scp_sftp_recursive;
+static unsigned long scp_sftp_mtime, scp_sftp_atime;
+static int scp_has_times;
+static struct fxp_handle *scp_sftp_filehandle;
+static struct fxp_xfer *scp_sftp_xfer;
+static uint64 scp_sftp_fileoffset;
+
+void scp_source_setup(char *target, int shouldbedir)
+{
+ if (using_sftp) {
+ /*
+ * Find out whether the target filespec is in fact a
+ * directory.
+ */
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ struct fxp_attrs attrs;
+ int ret;
+
+ if (!fxp_init()) {
+ tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
+ errs++;
+ return;
+ }
+
+ sftp_register(req = fxp_stat_send(target));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ ret = fxp_stat_recv(pktin, rreq, &attrs);
+
+ if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS))
+ scp_sftp_targetisdir = 0;
+ 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 = 0;
+ } else {
+ (void) response();
+ }
+}
+
+int scp_send_errmsg(char *str)
+{
+ if (using_sftp) {
+ /* do nothing; we never need to send our errors to the server */
+ } else {
+ back->send(backhandle, "\001", 1);/* scp protocol error prefix */
+ back->send(backhandle, str, strlen(str));
+ }
+ return 0; /* can't fail */
+}
+
+int scp_send_filetimes(unsigned long mtime, unsigned long atime)
+{
+ if (using_sftp) {
+ scp_sftp_mtime = mtime;
+ scp_sftp_atime = atime;
+ scp_has_times = 1;
+ return 0;
+ } else {
+ char buf[80];
+ sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
+ back->send(backhandle, buf, strlen(buf));
+ return response();
+ }
+}
+
+int scp_send_filename(char *name, unsigned long size, int modes)
+{
+ if (using_sftp) {
+ char *fullname;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+
+ if (scp_sftp_targetisdir) {
+ fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
+ } else {
+ fullname = dupstr(scp_sftp_remotepath);
+ }
+
+ sftp_register(req = fxp_open_send(fullname, SSH_FXF_WRITE |
+ SSH_FXF_CREAT | SSH_FXF_TRUNC));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ scp_sftp_filehandle = fxp_open_recv(pktin, rreq);
+
+ if (!scp_sftp_filehandle) {
+ tell_user(stderr, "pscp: unable to open %s: %s",
+ fullname, fxp_error());
+ errs++;
+ return 1;
+ }
+ scp_sftp_fileoffset = uint64_make(0, 0);
+ scp_sftp_xfer = xfer_upload_init(scp_sftp_filehandle,
+ scp_sftp_fileoffset);
+ sfree(fullname);
+ return 0;
+ } else {
+ char buf[40];
+ sprintf(buf, "C%04o %lu ", modes, size);
+ back->send(backhandle, buf, strlen(buf));
+ back->send(backhandle, name, strlen(name));
+ back->send(backhandle, "\n", 1);
+ return response();
+ }
+}
+
+int scp_send_filedata(char *data, int len)
+{
+ if (using_sftp) {
+ int ret;
+ struct sftp_packet *pktin;
+
+ if (!scp_sftp_filehandle) {
+ return 1;
+ }
+
+ while (!xfer_upload_ready(scp_sftp_xfer)) {
+ pktin = sftp_recv();
+ ret = xfer_upload_gotpkt(scp_sftp_xfer, pktin);
+ if (!ret) {
+ tell_user(stderr, "error while writing: %s\n", fxp_error());
+ errs++;
+ return 1;
+ }
+ }
+
+ xfer_upload_data(scp_sftp_xfer, data, len);
+
+ scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len);
+ return 0;
+ } else {
+ int bufsize = back->send(backhandle, 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 = back->sendbuffer(backhandle);
+ }
+
+ return 0;
+ }
+}
+
+int scp_send_finish(void)
+{
+ if (using_sftp) {
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int ret;
+
+ while (!xfer_done(scp_sftp_xfer)) {
+ pktin = sftp_recv();
+ xfer_upload_gotpkt(scp_sftp_xfer, pktin);
+ }
+ 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;
+ sftp_register(req = fxp_fsetstat_send(scp_sftp_filehandle, attrs));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ ret = fxp_fsetstat_recv(pktin, rreq);
+ if (!ret) {
+ tell_user(stderr, "unable to set file times: %s\n", fxp_error());
+ errs++;
+ }
+ }
+ sftp_register(req = fxp_close_send(scp_sftp_filehandle));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ fxp_close_recv(pktin, rreq);
+ scp_has_times = 0;
+ return 0;
+ } else {
+ back->send(backhandle, "", 1);
+ return response();
+ }
+}
+
+char *scp_save_remotepath(void)
+{
+ if (using_sftp)
+ return scp_sftp_remotepath;
+ else
+ return NULL;
+}
+
+void scp_restore_remotepath(char *data)
+{
+ if (using_sftp)
+ scp_sftp_remotepath = data;
+}
+
+int scp_send_dirname(char *name, int modes)
+{
+ if (using_sftp) {
+ char *fullname;
+ char const *err;
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int ret;
+
+ if (scp_sftp_targetisdir) {
+ fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
+ } 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.
+ */
+ sftp_register(req = fxp_mkdir_send(fullname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ ret = fxp_mkdir_recv(pktin, rreq);
+
+ if (!ret)
+ err = fxp_error();
+ else
+ err = "server reported no error";
+
+ sftp_register(req = fxp_stat_send(fullname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ ret = fxp_stat_recv(pktin, rreq, &attrs);
+
+ if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS) ||
+ !(attrs.permissions & 0040000)) {
+ tell_user(stderr, "unable to create directory %s: %s",
+ fullname, err);
+ errs++;
+ return 1;
+ }
+
+ scp_sftp_remotepath = fullname;
+
+ return 0;
+ } else {
+ char buf[40];
+ sprintf(buf, "D%04o 0 ", modes);
+ back->send(backhandle, buf, strlen(buf));
+ back->send(backhandle, name, strlen(name));
+ back->send(backhandle, "\n", 1);
+ return response();
+ }
+}
+
+int scp_send_enddir(void)
+{
+ if (using_sftp) {
+ sfree(scp_sftp_remotepath);
+ return 0;
+ } else {
+ back->send(backhandle, "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
+ * right at the start, whereas scp_sink_init is called to
+ * initialise every level of recursion in the protocol.
+ */
+int scp_sink_setup(char *source, int preserve, int recursive)
+{
+ if (using_sftp) {
+ char *newsource;
+
+ if (!fxp_init()) {
+ tell_user(stderr, "unable to initialise SFTP: %s", fxp_error());
+ errs++;
+ return 1;
+ }
+ /*
+ * It's possible that the source string we've been given
+ * contains a wildcard. If so, we must split the directory
+ * away from the wildcard itself (throwing an error if any
+ * wildcardness comes before the final slash) and arrange
+ * things so that a dirstack entry will be set up.
+ */
+ newsource = snewn(1+strlen(source), char);
+ if (!wc_unescape(newsource, source)) {
+ /* Yes, here we go; it's a wildcard. Bah. */
+ char *dupsource, *lastpart, *dirpart, *wildcard;
+ dupsource = dupstr(source);
+ lastpart = stripslashes(dupsource, 0);
+ wildcard = dupstr(lastpart);
+ *lastpart = '\0';
+ if (*dupsource && dupsource[1]) {
+ /*
+ * The remains of dupsource are at least two
+ * characters long, meaning the pathname wasn't
+ * empty or just `/'. Hence, we remove the trailing
+ * slash.
+ */
+ lastpart[-1] = '\0';
+ } else if (!*dupsource) {
+ /*
+ * The remains of dupsource are _empty_ - the whole
+ * pathname was a wildcard. Hence we need to
+ * replace it with ".".
+ */
+ sfree(dupsource);
+ dupsource = dupstr(".");
+ }
+
+ /*
+ * Now we have separated our string into dupsource (the
+ * directory part) and wildcard. Both of these will
+ * need freeing at some point. Next step is to remove
+ * wildcard escapes from the directory part, throwing
+ * an error if it contains a real wildcard.
+ */
+ dirpart = snewn(1+strlen(dupsource), char);
+ if (!wc_unescape(dirpart, dupsource)) {
+ tell_user(stderr, "%s: multiple-level wildcards unsupported",
+ source);
+ errs++;
+ sfree(dirpart);
+ sfree(wildcard);
+ sfree(dupsource);
+ return 1;
+ }
+
+ /*
+ * Now we have dirpart (unescaped, ie a valid remote
+ * path), and wildcard (a wildcard). This will be
+ * sufficient to arrange a dirstack entry.
+ */
+ scp_sftp_remotepath = dirpart;
+ scp_sftp_wildcard = wildcard;
+ sfree(dupsource);
+ } else {
+ scp_sftp_remotepath = newsource;
+ scp_sftp_wildcard = NULL;
+ }
+ scp_sftp_preserve = preserve;
+ scp_sftp_recursive = recursive;
+ scp_sftp_donethistarget = 0;
+ scp_sftp_dirstack_head = NULL;
+ }
+ return 0;
+}
+
+int scp_sink_init(void)
+{
+ if (!using_sftp) {
+ back->send(backhandle, "", 1);
+ }
+ return 0;
+}
+
+#define SCP_SINK_FILE 1
+#define SCP_SINK_DIR 2
+#define SCP_SINK_ENDDIR 3
+#define SCP_SINK_RETRY 4 /* not an action; just try again */
+struct scp_sink_action {
+ int action; /* FILE, DIR, ENDDIR */
+ char *buf; /* will need freeing after use */
+ char *name; /* filename or dirname (not ENDDIR) */
+ int mode; /* access mode (not ENDDIR) */
+ unsigned long size; /* file size (not ENDDIR) */
+ int settime; /* 1 if atime and mtime are filled */
+ unsigned long atime, mtime; /* access times for the file */
+};
+
+int scp_get_sink_action(struct scp_sink_action *act)
+{
+ if (using_sftp) {
+ char *fname;
+ int must_free_fname;
+ struct fxp_attrs attrs;
+ struct sftp_packet *pktin;
+ struct sftp_request *req, *rreq;
+ int ret;
+
+ if (!scp_sftp_dirstack_head) {
+ if (!scp_sftp_donethistarget) {
+ /*
+ * Simple case: we are only dealing with one file.
+ */
+ fname = scp_sftp_remotepath;
+ must_free_fname = 0;
+ scp_sftp_donethistarget = 1;
+ } else {
+ /*
+ * Even simpler case: one file _which we've done_.
+ * Return 1 (finished).
+ */
+ return 1;
+ }
+ } else {
+ /*
+ * We're now in the middle of stepping through a list
+ * of names returned from fxp_readdir(); so let's carry
+ * on.
+ */
+ struct scp_sftp_dirstack *head = scp_sftp_dirstack_head;
+ while (head->namepos < head->namelen &&
+ (is_dots(head->names[head->namepos].filename) ||
+ (head->wildcard &&
+ !wc_match(head->wildcard,
+ head->names[head->namepos].filename))))
+ head->namepos++; /* skip . and .. */
+ if (head->namepos < head->namelen) {
+ head->matched_something = 1;
+ fname = dupcat(head->dirpath, "/",
+ head->names[head->namepos++].filename,
+ NULL);
+ must_free_fname = 1;
+ } else {
+ /*
+ * We've come to the end of the list; pop it off
+ * the stack and return an ENDDIR action (or RETRY
+ * if this was a wildcard match).
+ */
+ if (head->wildcard) {
+ act->action = SCP_SINK_RETRY;
+ if (!head->matched_something) {
+ tell_user(stderr, "pscp: wildcard '%s' matched "
+ "no files", head->wildcard);
+ errs++;
+ }
+ sfree(head->wildcard);
+
+ } else {
+ act->action = SCP_SINK_ENDDIR;
+ }
+
+ sfree(head->dirpath);
+ sfree(head->names);
+ scp_sftp_dirstack_head = head->next;
+ sfree(head);
+
+ return 0;
+ }
+ }
+
+ /*
+ * Now we have a filename. Stat it, and see if it's a file
+ * or a directory.
+ */
+ sftp_register(req = fxp_stat_send(fname));
+ rreq = sftp_find_request(pktin = sftp_recv());
+ assert(rreq == req);
+ ret = fxp_stat_recv(pktin, rreq, &attrs);
+
+ if (!ret || !(attrs.flags & SSH_FILEXFER_ATTR_PERMISSIONS)) {
+ tell_user(stderr, "unable to identify %s: %s", fname,
+ ret ? "file type not supplied" : fxp_error());
+ errs++;
+ return 1;
+ }
+
+ if (attrs.permissions & 0040000) {
+ struct scp_sftp_dirstack *newitem;
+ struct fxp_handle *dirhandle;
+ int nnames, namesize;
+ struct fxp_name *ournames;
+ struct fxp_names *names;