+ free(etastr);
+}
+
+/*
+ * 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[1] == ':')
+ return (NULL);
+ while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\')
+ str++;
+ if (*str == ':')
+ return (str);
+ else
+ return (NULL);
+}
+
+/*
+ * 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();
+ }