#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <limits.h>
#include <time.h>
#include <assert.h>
/* GUI Adaptation - Sept 2000 */
#define PUTTY_DO_GLOBALS
#include "putty.h"
+#include "ssh.h"
+#include "sftp.h"
#include "winstuff.h"
#include "storage.h"
static int statistics = 1;
static int portnumber = 0;
static int prev_stats_len = 0;
+static int scp_unsafe_mode = 0;
static char *password = NULL;
static int errs = 0;
/* GUI Adaptation - Sept 2000 */
static unsigned long statelapsed = 0;
static int gui_mode = 0;
static char *gui_hwnd = NULL;
+static int using_sftp = 0;
static void source(char *src);
static void rsource(char *src);
return; /* doom */
select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
}
+ using_sftp = !ssh_fallback_cmd;
}
/*
if (portnumber)
cfg.port = portnumber;
- strncpy(cfg.remote_cmd, cmd, sizeof(cfg.remote_cmd));
- cfg.remote_cmd[sizeof(cfg.remote_cmd) - 1] = '\0';
+ /*
+ * Attempt to start the SFTP subsystem as a first choice,
+ * falling back to the provided scp command if that fails.
+ */
+ strcpy(cfg.remote_cmd, "sftp");
+ cfg.ssh_subsys = TRUE;
+ cfg.remote_cmd_ptr2 = cmd;
+ cfg.ssh_subsys2 = FALSE;
cfg.nopty = TRUE;
back = &ssh_backend;
}
/*
+ * Return a pointer to the portion of str that comes after the last
+ * slash (or backslash, if `local' is TRUE).
+ */
+static char *stripslashes(char *str, int local)
+{
+ char *p;
+
+ 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.
*/
}
}
+int sftp_recvdata(char *buf, int len)
+{
+ return ssh_scp_recv(buf, len);
+}
+int sftp_senddata(char *buf, int len)
+{
+ back->send((unsigned char *) 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;
+ int nnames, namesize;
+ int i;
+
+ printf("Listing directory %s\n", dirname);
+
+ dirh = fxp_opendir(dirname);
+ if (dirh == NULL) {
+ printf("Unable to open %s: %s\n", dirname, fxp_error());
+ } else {
+ nnames = namesize = 0;
+ ournames = NULL;
+
+ while (1) {
+
+ names = fxp_readdir(dirh);
+ 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 =
+ 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(dirh);
+
+ /*
+ * 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,
- * so they can be switched to use SFTP.
+ * 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;
+} *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 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 fxp_attrs attrs;
+
+ if (!fxp_stat(target, &attrs) ||
+ !(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)
{
- back->send("\001", 1); /* scp protocol error prefix */
- back->send(str, strlen(str));
+ if (using_sftp) {
+ /* do nothing; we never need to send our errors to the server */
+ } else {
+ back->send("\001", 1); /* scp protocol error prefix */
+ back->send(str, strlen(str));
+ }
return 0; /* can't fail */
}
int scp_send_filetimes(unsigned long mtime, unsigned long atime)
{
- char buf[80];
- sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime);
- back->send(buf, strlen(buf));
- return response();
+ 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(buf, strlen(buf));
+ return response();
+ }
}
int scp_send_filename(char *name, unsigned long size, int modes)
{
- char buf[40];
- sprintf(buf, "C%04o %lu ", modes, size);
- back->send(buf, strlen(buf));
- back->send(name, strlen(name));
- back->send("\n", 1);
- return response();
+ if (using_sftp) {
+ char *fullname;
+ if (scp_sftp_targetisdir) {
+ fullname = dupcat(scp_sftp_remotepath, "/", name, NULL);
+ } else {
+ fullname = dupstr(scp_sftp_remotepath);
+ }
+ scp_sftp_filehandle =
+ fxp_open(fullname, SSH_FXF_WRITE | SSH_FXF_CREAT | SSH_FXF_TRUNC);
+ 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);
+ sfree(fullname);
+ return 0;
+ } else {
+ char buf[40];
+ sprintf(buf, "C%04o %lu ", modes, size);
+ back->send(buf, strlen(buf));
+ back->send(name, strlen(name));
+ back->send("\n", 1);
+ return response();
+ }
}
int scp_send_filedata(char *data, int len)
{
- int bufsize = back->send(data, len);
+ if (using_sftp) {
+ if (!scp_sftp_filehandle) {
+ return 1;
+ }
+ if (!fxp_write(scp_sftp_filehandle, data, scp_sftp_fileoffset, len)) {
+ tell_user(stderr, "error while writing: %s\n", fxp_error());
+ errs++;
+ return 1;
+ }
+ scp_sftp_fileoffset = uint64_add32(scp_sftp_fileoffset, len);
+ return 0;
+ } else {
+ int bufsize = back->send(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 (!scp_process_network_event())
+ /*
+ * 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 (!scp_process_network_event())
+ return 1;
+ bufsize = back->sendbuffer();
+ }
+
+ return 0;
+ }
+}
+
+int scp_send_finish(void)
+{
+ if (using_sftp) {
+ struct fxp_attrs attrs;
+ if (!scp_sftp_filehandle) {
return 1;
- bufsize = back->sendbuffer();
+ }
+ if (scp_has_times) {
+ attrs.flags = SSH_FILEXFER_ATTR_ACMODTIME;
+ attrs.atime = scp_sftp_atime;
+ attrs.mtime = scp_sftp_mtime;
+ if (!fxp_fsetstat(scp_sftp_filehandle, attrs)) {
+ tell_user(stderr, "unable to set file times: %s\n", fxp_error());
+ errs++;
+ }
+ }
+ fxp_close(scp_sftp_filehandle);
+ scp_has_times = 0;
+ return 0;
+ } else {
+ back->send("", 1);
+ return response();
}
+}
- return 0;
+char *scp_save_remotepath(void)
+{
+ if (using_sftp)
+ return scp_sftp_remotepath;
+ else
+ return NULL;
}
-int scp_send_finish(void)
+void scp_restore_remotepath(char *data)
{
- back->send("", 1);
- return response();
+ if (using_sftp)
+ scp_sftp_remotepath = data;
}
int scp_send_dirname(char *name, int modes)
{
- char buf[40];
- sprintf(buf, "D%04o 0 ", modes);
- back->send(buf, strlen(buf));
- back->send(name, strlen(name));
- back->send("\n", 1);
- return response();
+ if (using_sftp) {
+ char *fullname;
+ char const *err;
+ struct fxp_attrs attrs;
+ 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.
+ */
+ if (!fxp_mkdir(fullname))
+ err = fxp_error();
+ else
+ err = "server reported no error";
+ if (!fxp_stat(fullname, &attrs) ||
+ !(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(buf, strlen(buf));
+ back->send(name, strlen(name));
+ back->send("\n", 1);
+ return response();
+ }
}
int scp_send_enddir(void)
{
- back->send("E\n", 2);
- return response();
+ if (using_sftp) {
+ sfree(scp_sftp_remotepath);
+ return 0;
+ } else {
+ back->send("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;
+ /*
+ * 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 = smalloc(1+strlen(source));
+ 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 = smalloc(1+strlen(dupsource));
+ 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)
{
- back->send("", 1);
+ if (!using_sftp) {
+ back->send("", 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 */
int scp_get_sink_action(struct scp_sink_action *act)
{
- int done = 0;
- int i, bufsize;
- int action;
- char ch;
+ 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->wildcard &&
+ !wc_match(head->wildcard,
+ 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 (or RETRY
+ * if this was a wildcard match).
+ */
+ if (head->wildcard) {
+ act->action = SCP_SINK_RETRY;
+ sfree(head->wildcard);
+ } else {
+ act->action = SCP_SINK_ENDDIR;
+ }
- act->settime = 0;
- act->buf = NULL;
- bufsize = 0;
+ sfree(head->dirpath);
+ sfree(head->names);
+ scp_sftp_dirstack_head = head->next;
+ sfree(head);
- 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);
+ return 0;
}
- act->buf[i++] = ch;
- } while (ch != '\n');
- act->buf[i - 1] = '\0';
- switch (action) {
- case '\01': /* error */
- tell_user(stderr, "%s\n", act->buf);
+ }
+
+ /*
+ * 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++;
- continue; /* go round again */
- case '\02': /* fatal error */
- bump("%s", act->buf);
- case 'E':
- back->send("", 1);
- act->action = SCP_SINK_ENDDIR;
+ 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 merits a complaint (which is fatal if the name
+ * was specified directly, but not if it was matched by
+ * a wildcard).
+ *
+ * We skip this complaint completely if
+ * scp_sftp_wildcard is set, because that's an
+ * indication that we're not actually supposed to
+ * _recursively_ transfer the dir, just scan it for
+ * things matching the wildcard.
+ */
+ if (!scp_sftp_recursive && !scp_sftp_wildcard) {
+ tell_user(stderr, "pscp: %s: is a directory", fname);
+ errs++;
+ if (must_free_fname) sfree(fname);
+ if (scp_sftp_dirstack_head) {
+ act->action = SCP_SINK_RETRY;
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
+ /*
+ * Otherwise, the fun begins. We must fxp_opendir() the
+ * directory, slurp the filenames into memory, return
+ * SCP_SINK_DIR (unless this is a wildcard match), and
+ * set targetisdir. The next time we're called, we will
+ * run through the list of filenames one by one,
+ * matching them against a wildcard if present.
+ *
+ * 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);
+ if (scp_sftp_wildcard) {
+ newitem->wildcard = scp_sftp_wildcard;
+ scp_sftp_wildcard = NULL;
+ } else {
+ newitem->wildcard = NULL;
+ }
+ scp_sftp_dirstack_head = newitem;
+
+ if (newitem->wildcard) {
+ act->action = SCP_SINK_RETRY;
+ } else {
+ act->action = SCP_SINK_DIR;
+ act->buf = dupstr(stripslashes(fname, 0));
+ 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;
- case 'T':
- if (sscanf(act->buf, "%ld %*d %ld %*d",
- &act->mtime, &act->atime) == 2) {
+
+ } else {
+ /*
+ * It's a file. Return SCP_SINK_FILE.
+ */
+ act->action = SCP_SINK_FILE;
+ act->buf = dupstr(stripslashes(fname, 0));
+ 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);
- continue; /* go round again */
+ 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");
}
- 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;
}
+
/*
- * We will go round this loop only once, unless we hit
- * `continue' above.
+ * If we get here, we must have seen SCP_SINK_FILE or
+ * SCP_SINK_DIR.
*/
- done = 1;
+ 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;
}
-
- /*
- * 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)
{
- back->send("", 1);
- return 0; /* can't fail */
+ 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)
{
- return ssh_scp_recv(data, 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)
{
- back->send("", 1);
- return response();
+ if (using_sftp) {
+ fxp_close(scp_sftp_filehandle);
+ return 0;
+ } else {
+ back->send("", 1);
+ return response();
+ }
}
/* ----------------------------------------------------------------------
*/
static void source(char *src)
{
- char buf[2048];
unsigned long size;
char *last;
HANDLE f;
*/
static void rsource(char *src)
{
- char buf[2048];
- char *last;
+ char *last, *findfile;
+ char *save_target;
HANDLE dir;
WIN32_FIND_DATA fdat;
int ok;
/* maybe send filetime */
+ save_target = scp_save_remotepath();
+
if (verbose)
tell_user(stderr, "Entering directory: %s", last);
if (scp_send_dirname(last, 0755))
return;
- sprintf(buf, "%s/*", src);
- dir = FindFirstFile(buf, &fdat);
+ findfile = dupcat(src, "/*", NULL);
+ dir = FindFirstFile(findfile, &fdat);
ok = (dir != INVALID_HANDLE_VALUE);
while (ok) {
if (strcmp(fdat.cFileName, ".") == 0 ||
strcmp(fdat.cFileName, "..") == 0) {
- } else if (strlen(src) + 1 + strlen(fdat.cFileName) >= sizeof(buf)) {
- run_err("%s/%s: Name too long", src, fdat.cFileName);
+ /* ignore . and .. */
} else {
- sprintf(buf, "%s/%s", src, fdat.cFileName);
- source(buf);
+ char *foundfile = dupcat(src, "/", fdat.cFileName, NULL);
+ source(foundfile);
+ sfree(foundfile);
}
ok = FindNextFile(dir, &fdat);
}
FindClose(dir);
+ sfree(findfile);
(void) scp_send_enddir();
+
+ scp_restore_remotepath(save_target);
}
/*
- * Execute the sink part of the SCP protocol.
+ * Execute the sink part of the SCP protocol.
*/
static void sink(char *targ, char *src)
{
- char buf[2048];
- char namebuf[2048];
- char ch;
+ char *destfname;
int targisdir = 0;
- int settime;
int exists;
DWORD attr;
HANDLE f;
if (act.action == SCP_SINK_ENDDIR)
return;
- /* Security fix: ensure the file ends up where we asked for it. */
+
+ if (act.action == SCP_SINK_RETRY)
+ continue;
+
if (targisdir) {
- char t[2048];
- char *p;
- strcpy(t, targ);
+ /*
+ * Prevent the remote side from maliciously writing to
+ * files outside the target area by sending a filename
+ * containing `../'. In fact, it shouldn't be sending
+ * filenames with any slashes in at all; so we'll find
+ * the last slash or backslash in the filename and use
+ * only the part after that. (And warn!)
+ *
+ * In addition, we also ensure here that if we're
+ * copying a single file and the target is a directory
+ * (common usage: `pscp host:filename .') the remote
+ * can't send us a _different_ file name. We can
+ * distinguish this case because `src' will be non-NULL
+ * and the last component of that will fail to match
+ * (the last component of) the name sent.
+ *
+ * Well, not always; if `src' is a wildcard, we do
+ * expect to get back filenames that don't correspond
+ * exactly to it. Ideally in this case, we would like
+ * to ensure that the returned filename actually
+ * matches the wildcard pattern - but one of SCP's
+ * protocol infelicities is that wildcard matching is
+ * done at the server end _by the server's rules_ and
+ * so in general this is infeasible. Hence, we only
+ * accept filenames that don't correspond to `src' if
+ * unsafe mode is enabled or we are using SFTP (which
+ * resolves remote wildcards on the client side and can
+ * be trusted).
+ */
+ char *striptarget, *stripsrc;
+
+ striptarget = stripslashes(act.name, 1);
+ if (striptarget != act.name) {
+ tell_user(stderr, "warning: remote host sent a compound"
+ " pathname - possibly malicious! (ignored)");
+ }
+
+ /*
+ * Also check to see if the target filename is '.' or
+ * '..', or indeed '...' and so on because Windows
+ * appears to interpret those like '..'.
+ */
+ if (is_dots(striptarget)) {
+ bump("security violation: remote host attempted to write to"
+ " a '.' or '..' path!");
+ }
+
+ if (src) {
+ stripsrc = stripslashes(src, 1);
+ if (strcmp(striptarget, stripsrc) &&
+ !using_sftp && !scp_unsafe_mode) {
+ tell_user(stderr, "warning: remote host tried to write "
+ "to a file called '%s'", striptarget);
+ tell_user(stderr, " when we requested a file "
+ "called '%s'.", stripsrc);
+ tell_user(stderr, " If this is a wildcard, "
+ "consider upgrading to SSH 2 or using");
+ tell_user(stderr, " the '-unsafe' option. Renaming"
+ " of this file has been disallowed.");
+ /* Override the name the server provided with our own. */
+ striptarget = stripsrc;
+ }
+ }
+
if (targ[0] != '\0')
- strcat(t, "/");
- p = act.name + strlen(act.name);
- while (p > act.name && p[-1] != '/' && p[-1] != '\\')
- p--;
- strcat(t, p);
- strcpy(namebuf, t);
+ destfname = dupcat(targ, "\\", striptarget, NULL);
+ else
+ destfname = dupstr(striptarget);
} else {
- strcpy(namebuf, targ);
+ /*
+ * In this branch of the if, the target area is a
+ * single file with an explicitly specified name in any
+ * case, so there's no danger.
+ */
+ destfname = dupstr(targ);
}
- attr = GetFileAttributes(namebuf);
+ attr = GetFileAttributes(destfname);
exists = (attr != (DWORD) - 1);
if (act.action == SCP_SINK_DIR) {
if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
- run_err("%s: Not a directory", namebuf);
+ run_err("%s: Not a directory", destfname);
continue;
}
if (!exists) {
- if (!CreateDirectory(namebuf, NULL)) {
- run_err("%s: Cannot create directory", namebuf);
+ if (!CreateDirectory(destfname, NULL)) {
+ run_err("%s: Cannot create directory", destfname);
continue;
}
}
- sink(namebuf, NULL);
+ sink(destfname, NULL);
/* can we set the timestamp for directories ? */
continue;
}
- f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL,
+ f = CreateFile(destfname, GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (f == INVALID_HANDLE_VALUE) {
- run_err("%s: Cannot create file", namebuf);
+ run_err("%s: Cannot create file", destfname);
continue;
}
stat_bytes = 0;
stat_starttime = time(NULL);
stat_lasttime = 0;
- if ((stat_name = strrchr(namebuf, '/')) == NULL)
- stat_name = namebuf;
- else
- stat_name++;
- if (strrchr(stat_name, '\\') != NULL)
- stat_name = strrchr(stat_name, '\\') + 1;
+ stat_name = stripslashes(destfname, 1);
received = 0;
while (received < act.size) {
CloseHandle(f);
if (wrerror) {
- run_err("%s: Write error", namebuf);
+ run_err("%s: Write error", destfname);
continue;
}
(void) scp_finish_filerecv();
+ sfree(destfname);
+ sfree(act.name);
}
}
do_cmd(host, user, cmd);
sfree(cmd);
- (void) response();
+ scp_source_setup(targ, targetshouldbedirectory);
for (i = 0; i < argc - 1; i++) {
+ char *srcpath, *last;
HANDLE dir;
WIN32_FIND_DATA fdat;
src = argv[i];
errs++;
continue;
}
+
+ /*
+ * Trim off the last pathname component of `src', to
+ * provide the base pathname which will be prepended to
+ * filenames returned from Find{First,Next}File.
+ */
+ srcpath = dupstr(src);
+ last = stripslashes(srcpath, 1);
+ if (last == srcpath) {
+ last = strchr(srcpath, ':');
+ if (last)
+ last++;
+ else
+ last = srcpath;
+ }
+ *last = '\0';
+
dir = FindFirstFile(src, &fdat);
if (dir == INVALID_HANDLE_VALUE) {
run_err("%s: No such file or directory", src);
continue;
}
do {
- char *last;
- char namebuf[2048];
+ char *filename;
/*
* Ensure that . and .. are never matched by wildcards,
* but only by deliberate action.
} else
continue; /* ignore this one */
}
- if (strlen(src) + strlen(fdat.cFileName) >= sizeof(namebuf)) {
- tell_user(stderr, "%s: Name too long", src);
- continue;
- }
- strcpy(namebuf, src);
- if ((last = strrchr(namebuf, '/')) == NULL)
- last = namebuf;
- else
- last++;
- if (strrchr(last, '\\') != NULL)
- last = strrchr(last, '\\') + 1;
- if (last == namebuf && strrchr(namebuf, ':') != NULL)
- last = strchr(namebuf, ':') + 1;
- strcpy(last, fdat.cFileName);
- source(namebuf);
+ filename = dupcat(srcpath, fdat.cFileName, NULL);
+ source(filename);
+ sfree(filename);
} while (FindNextFile(dir, &fdat));
FindClose(dir);
+ sfree(srcpath);
}
}
do_cmd(host, user, cmd);
sfree(cmd);
+ if (scp_sink_setup(src, preserve, recursive))
+ return;
+
sink(targ, src);
}
do_cmd(host, user, cmd);
sfree(cmd);
- while (ssh_scp_recv(&c, 1) > 0)
- tell_char(stdout, c);
+ if (using_sftp) {
+ scp_sftp_listdir(src);
+ } else {
+ while (ssh_scp_recv(&c, 1) > 0)
+ tell_char(stdout, c);
+ }
}
/*
printf(" -v show verbose messages\n");
printf(" -P port connect to specified port\n");
printf(" -pw passw login with specified password\n");
+ printf(" -unsafe allow server-side wildcards (DANGEROUS)\n");
#if 0
/*
* -gui is an internal option, used by GUI front ends to get
gui_mode = 1;
} else if (strcmp(argv[i], "-ls") == 0)
list = 1;
+ else if (strcmp(argv[i], "-unsafe") == 0)
+ scp_unsafe_mode = 1;
else if (strcmp(argv[i], "--") == 0) {
i++;
break;