+ * 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.
+ */
+void scp_sink_setup(char *source, int preserve, int recursive)
+{
+ if (using_sftp) {
+ scp_sftp_remotepath = dupstr(source);
+ scp_sftp_preserve = preserve;
+ scp_sftp_recursive = recursive;
+ scp_sftp_donethistarget = 0;
+ scp_sftp_dirstack_head = NULL;
+ }
+}
+
+int scp_sink_init(void)
+{
+ if (!using_sftp) {
+ back->send("", 1);
+ }
+ return 0;
+}
+
+#define SCP_SINK_FILE 1
+#define SCP_SINK_DIR 2
+#define SCP_SINK_ENDDIR 3
+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;
+ 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->namepos++; /* skip . and .. */
+ if (head->namepos < head->namelen) {
+ 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.
+ */
+
+ sfree(head->dirpath);
+ sfree(head->names);
+ scp_sftp_dirstack_head = head->next;
+ sfree(head);
+
+ act->action = SCP_SINK_ENDDIR;
+ return 0;
+ }
+ }
+
+ /*
+ * Now we have a filename. Stat it, and see if it's a file
+ * or a directory.
+ */
+ ret = fxp_stat(fname, &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;
+
+ /*
+ * It's a directory. If we're not in recursive
+ * mode, this just merits a complaint.
+ */
+ if (!scp_sftp_recursive) {
+ tell_user(stderr, "pscp: %s: is a directory", fname);
+ errs++;
+ if (must_free_fname) sfree(fname);
+ return 1;
+ }
+
+ /*
+ * Otherwise, the fun begins. We must fxp_opendir() the
+ * directory, slurp the filenames into memory, return
+ * SCP_SINK_DIR, and set targetisdir. The next time
+ * we're called, we will run through the list of
+ * filenames one by one.
+ *
+ * If targetisdir is _already_ set (meaning we're
+ * already in the middle of going through another such
+ * list), we must push the other (target,namelist) pair
+ * on a stack.
+ */
+ dirhandle = fxp_opendir(fname);
+ if (!dirhandle) {
+ tell_user(stderr, "scp: unable to open directory %s: %s",
+ fname, fxp_error());
+ if (must_free_fname) sfree(fname);
+ errs++;
+ return 1;
+ }
+ nnames = namesize = 0;
+ ournames = NULL;
+ while (1) {
+ int i;
+
+ names = fxp_readdir(dirhandle);
+ if (names == NULL) {
+ if (fxp_error_type() == SSH_FX_EOF)
+ break;
+ tell_user(stderr, "scp: reading directory %s: %s\n",
+ fname, fxp_error());
+ if (must_free_fname) sfree(fname);
+ sfree(ournames);
+ errs++;
+ return 1;
+ }
+ if (names->nnames == 0) {
+ fxp_free_names(names);
+ break;
+ }
+ if (nnames + names->nnames >= namesize) {
+ namesize += names->nnames + 128;
+ ournames =
+ srealloc(ournames, namesize * sizeof(*ournames));
+ }
+ for (i = 0; i < names->nnames; i++)
+ ournames[nnames++] = names->names[i];
+ names->nnames = 0; /* prevent free_names */
+ fxp_free_names(names);
+ }
+ fxp_close(dirhandle);
+
+ newitem = smalloc(sizeof(struct scp_sftp_dirstack));
+ newitem->next = scp_sftp_dirstack_head;
+ newitem->names = ournames;
+ newitem->namepos = 0;
+ newitem->namelen = nnames;
+ if (must_free_fname)
+ newitem->dirpath = fname;
+ else
+ newitem->dirpath = dupstr(fname);
+ scp_sftp_dirstack_head = newitem;
+
+ act->action = SCP_SINK_DIR;
+ act->buf = dupstr(stripslashes(fname));
+ act->name = act->buf;
+ act->size = 0; /* duhh, it's a directory */
+ act->mode = 07777 & attrs.permissions;
+ if (scp_sftp_preserve &&
+ (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
+ act->atime = attrs.atime;
+ act->mtime = attrs.mtime;
+ act->settime = 1;
+ } else
+ act->settime = 0;
+ return 0;
+
+ } else {
+ /*
+ * It's a file. Return SCP_SINK_FILE.
+ */
+ act->action = SCP_SINK_FILE;
+ act->buf = dupstr(stripslashes(fname));
+ act->name = act->buf;
+ if (attrs.flags & SSH_FILEXFER_ATTR_SIZE) {
+ if (uint64_compare(attrs.size,
+ uint64_make(0, ULONG_MAX)) > 0) {
+ act->size = ULONG_MAX; /* *boggle* */
+ } else
+ act->size = attrs.size.lo;
+ } else
+ act->size = ULONG_MAX; /* no idea */
+ act->mode = 07777 & attrs.permissions;
+ if (scp_sftp_preserve &&
+ (attrs.flags & SSH_FILEXFER_ATTR_ACMODTIME)) {
+ act->atime = attrs.atime;
+ act->mtime = attrs.mtime;
+ act->settime = 1;
+ } else
+ act->settime = 0;
+ if (must_free_fname)
+ scp_sftp_currentname = fname;
+ else
+ scp_sftp_currentname = dupstr(fname);
+ return 0;
+ }
+
+ } else {
+ int done = 0;
+ int i, bufsize;
+ int action;
+ char ch;
+
+ act->settime = 0;
+ act->buf = NULL;
+ bufsize = 0;
+
+ while (!done) {
+ if (ssh_scp_recv(&ch, 1) <= 0)
+ return 1;
+ if (ch == '\n')
+ bump("Protocol error: Unexpected newline");
+ i = 0;
+ action = ch;
+ do {
+ if (ssh_scp_recv(&ch, 1) <= 0)
+ bump("Lost connection");
+ if (i >= bufsize) {
+ bufsize = i + 128;
+ act->buf = srealloc(act->buf, bufsize);
+ }
+ act->buf[i++] = ch;
+ } while (ch != '\n');
+ act->buf[i - 1] = '\0';
+ switch (action) {
+ case '\01': /* error */
+ tell_user(stderr, "%s\n", act->buf);
+ errs++;
+ continue; /* go round again */
+ case '\02': /* fatal error */
+ bump("%s", act->buf);
+ case 'E':
+ back->send("", 1);
+ act->action = SCP_SINK_ENDDIR;
+ return 0;
+ case 'T':
+ if (sscanf(act->buf, "%ld %*d %ld %*d",
+ &act->mtime, &act->atime) == 2) {
+ act->settime = 1;
+ back->send("", 1);
+ continue; /* go round again */
+ }
+ bump("Protocol error: Illegal time format");
+ case 'C':
+ case 'D':
+ act->action = (action == 'C' ? SCP_SINK_FILE : SCP_SINK_DIR);
+ break;
+ default:
+ bump("Protocol error: Expected control record");
+ }
+ /*
+ * We will go round this loop only once, unless we hit
+ * `continue' above.
+ */
+ done = 1;
+ }
+
+ /*
+ * If we get here, we must have seen SCP_SINK_FILE or
+ * SCP_SINK_DIR.
+ */
+ if (sscanf(act->buf, "%o %lu %n", &act->mode, &act->size, &i) != 2)
+ bump("Protocol error: Illegal file descriptor format");
+ act->name = act->buf + i;
+ return 0;
+ }
+}
+
+int scp_accept_filexfer(void)
+{
+ if (using_sftp) {
+ scp_sftp_filehandle =
+ fxp_open(scp_sftp_currentname, SSH_FXF_READ);
+ if (!scp_sftp_filehandle) {
+ tell_user(stderr, "pscp: unable to open %s: %s",
+ scp_sftp_currentname, fxp_error());
+ errs++;
+ return 1;
+ }
+ scp_sftp_fileoffset = uint64_make(0, 0);
+ sfree(scp_sftp_currentname);
+ return 0;
+ } else {
+ back->send("", 1);
+ return 0; /* can't fail */
+ }
+}
+
+int scp_recv_filedata(char *data, int len)
+{
+ if (using_sftp) {
+ int actuallen = fxp_read(scp_sftp_filehandle, data,
+ scp_sftp_fileoffset, len);
+ if (actuallen == -1 && fxp_error_type() != SSH_FX_EOF) {
+ tell_user(stderr, "pscp: error while reading: %s", fxp_error());
+ errs++;
+ return -1;
+ }
+ if (actuallen < 0)
+ actuallen = 0;
+
+ scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, actuallen);
+
+ return actuallen;
+ } else {
+ return ssh_scp_recv(data, len);
+ }
+}
+
+int scp_finish_filerecv(void)
+{
+ if (using_sftp) {
+ fxp_close(scp_sftp_filehandle);
+ return 0;
+ } else {
+ back->send("", 1);
+ return response();
+ }
+}
+
+/* ----------------------------------------------------------------------