1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 09:27:59 +00:00

PSCP now uses the modern SFTP protocol if it can, and falls back to

scp1 if it can't. Currently not very tested - I checked it in as
soon as it completed a successful recursive copy in both directions.
Also, one known bug: you can't specify a remote wildcard, because by
the nature of SFTP we'll need to implement the wildcard engine on
the client side. I do intend to do this (and use the same wildcard
engine in PSFTP as well) but I haven't got round to it yet.

[originally from svn r1208]
This commit is contained in:
Simon Tatham 2001-08-26 18:32:28 +00:00
parent 605fa91201
commit ff9a038cdd
9 changed files with 756 additions and 153 deletions

View File

@ -90,7 +90,9 @@ PLOBJS = plink.$(OBJ)
##-- objects pscp
SOBJS = scp.$(OBJ) winnet.$(OBJ) be_none.$(OBJ)
##-- objects psftp
FOBJS = psftp.$(OBJ) sftp.$(OBJ) int64.$(OBJ) winnet.$(OBJ) be_none.$(OBJ)
FOBJS = psftp.$(OBJ) winnet.$(OBJ) be_none.$(OBJ)
##-- objects pscp psftp
SFOBJS = sftp.$(OBJ) int64.$(OBJ)
##-- objects putty puttytel pscp psftp plink
MOBJS = misc.$(OBJ) version.$(OBJ) winstore.$(OBJ) settings.$(OBJ)
MOBJ2 = tree234.$(OBJ)
@ -152,10 +154,10 @@ pageant.exe: $(PAGE1) $(PAGE2) $(PAGE3) $(PAGERC) pageant.rsp
puttygen.exe: $(GEN1) $(GEN2) $(GEN3) $(GEN4) $(GENRC) puttygen.rsp
link $(LFLAGS) -out:puttygen.exe -map:puttygen.map @puttygen.rsp
pscp.exe: $(SOBJS) $(MOBJS) $(MOBJ2) $(OBJS1) $(OBJS2) $(OBJS3) $(OBJS4) $(SRESRC) pscp.rsp
pscp.exe: $(SOBJS) $(SFOBJS) $(MOBJS) $(MOBJ2) $(OBJS1) $(OBJS2) $(OBJS3) $(OBJS4) $(SRESRC) pscp.rsp
link $(LFLAGS) -out:pscp.exe -map:pscp.map @pscp.rsp
psftp.exe: $(FOBJS) $(MOBJS) $(MOBJ2) $(OBJS1) $(OBJS2) $(OBJS3) $(OBJS4) $(SRESRC) psftp.rsp
psftp.exe: $(FOBJS) $(SFOBJS) $(MOBJS) $(MOBJ2) $(OBJS1) $(OBJS2) $(OBJS3) $(OBJS4) $(SRESRC) psftp.rsp
link $(LFLAGS) -out:psftp.exe -map:psftp.map @psftp.rsp
plink.exe: $(LOBJS1) $(POBJS) $(PLOBJS) $(MOBJS) $(MOBJ2) $(OBJS1) $(OBJS2) $(OBJS3) $(OBJS4) $(LRESRC) plink.rsp
@ -220,6 +222,7 @@ puttygen.rsp: makefile
pscp.rsp: makefile
echo /nologo /subsystem:console > pscp.rsp
echo $(SOBJS) >> pscp.rsp
echo $(SFOBJS) >> pscp.rsp
echo $(MOBJS) >> pscp.rsp
echo $(MOBJ2) >> pscp.rsp
echo $(OBJS1) >> pscp.rsp
@ -234,6 +237,7 @@ pscp.rsp: makefile
psftp.rsp: makefile
echo /nologo /subsystem:console > psftp.rsp
echo $(FOBJS) >> psftp.rsp
echo $(SFOBJS) >> psftp.rsp
echo $(MOBJS) >> psftp.rsp
echo $(MOBJ2) >> psftp.rsp
echo $(OBJS1) >> psftp.rsp

View File

@ -471,6 +471,7 @@ int main(int argc, char **argv)
command[cmdlen++] = d;
} while (c != EOF);
cfg.remote_cmd_ptr = command;
cfg.remote_cmd_ptr2 = NULL;
cfg.nopty = TRUE; /* command => no terminal */
} else if (!strcmp(p, "-P") && argc > 1) {
--argc, portnumber = atoi(*++argv);

View File

@ -247,8 +247,12 @@ typedef struct {
int ping_interval; /* in seconds */
/* SSH options */
char remote_cmd[512];
char remote_cmd2[512]; /* fallback if the first fails
* (used internally for scp) */
char *remote_cmd_ptr; /* might point to a larger command
* but never for loading/saving */
char *remote_cmd_ptr2; /* might point to a larger command
* but never for loading/saving */
int nopty;
int compression;
int agentfwd;
@ -258,6 +262,7 @@ typedef struct {
int buggymac; /* MAC bug commmercial <=v2.3.x SSH2 */
int try_tis_auth;
int ssh_subsys; /* run a subsystem rather than a command */
int ssh_subsys2; /* fallback to go with remote_cmd2 */
/* Telnet options */
char termtype[32];
char termspeed[32];

734
scp.c
View File

@ -19,6 +19,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <assert.h>
/* GUI Adaptation - Sept 2000 */
@ -27,6 +28,8 @@
#define PUTTY_DO_GLOBALS
#include "putty.h"
#include "ssh.h"
#include "sftp.h"
#include "winstuff.h"
#include "storage.h"
@ -64,6 +67,7 @@ static int statperct = 0;
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);
@ -463,6 +467,7 @@ static void ssh_scp_init(void)
return; /* doom */
select_result((WPARAM) scp_ssh_socket, (LPARAM) FD_READ);
}
using_sftp = !ssh_fallback_cmd;
}
/*
@ -596,8 +601,14 @@ static void do_cmd(char *host, char *user, char *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;
@ -689,6 +700,14 @@ static char *stripslashes(char *str)
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.
@ -725,80 +744,336 @@ static int response(void)
}
}
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;
char *dir;
int i;
printf("Listing directory %s\n", dirname);
dirh = fxp_opendir(dirname);
if (dirh == NULL) {
printf("Unable to open %s: %s\n", dir, 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", dir, 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;
} *scp_sftp_dirstack_head;
static char *scp_sftp_remotepath, *scp_sftp_currentname;
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 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 (using_sftp) {
if (!scp_sftp_filehandle) {
return 1;
bufsize = back->sendbuffer();
}
}
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);
return 0;
/*
* 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)
{
back->send("", 1);
return response();
if (using_sftp) {
struct fxp_attrs attrs;
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;
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();
}
}
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)
{
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.
*/
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)
{
back->send("", 1);
if (!using_sftp) {
back->send("", 1);
}
return 0;
}
@ -817,90 +1092,316 @@ struct scp_sink_action {
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;
act->settime = 0;
act->buf = NULL;
bufsize = 0;
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);
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->action = SCP_SINK_ENDDIR;
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);
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;
/*
* 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)
{
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();
}
}
/* ----------------------------------------------------------------------
@ -1034,6 +1535,7 @@ static void source(char *src)
static void rsource(char *src)
{
char *last, *findfile;
char *save_target;
HANDLE dir;
WIN32_FIND_DATA fdat;
int ok;
@ -1049,6 +1551,8 @@ static void rsource(char *src)
/* maybe send filetime */
save_target = scp_save_remotepath();
if (verbose)
tell_user(stderr, "Entering directory: %s", last);
if (scp_send_dirname(last, 0755))
@ -1062,7 +1566,7 @@ static void rsource(char *src)
strcmp(fdat.cFileName, "..") == 0) {
/* ignore . and .. */
} else {
char *foundfile = dupcat(src, "/", fdat.cFileName);
char *foundfile = dupcat(src, "/", fdat.cFileName, NULL);
source(foundfile);
sfree(foundfile);
}
@ -1072,6 +1576,8 @@ static void rsource(char *src)
sfree(findfile);
(void) scp_send_enddir();
scp_restore_remotepath(save_target);
}
/*
@ -1149,7 +1655,7 @@ static void sink(char *targ, char *src)
* '..', or indeed '...' and so on because Windows
* appears to interpret those like '..'.
*/
if (striptarget[strspn(striptarget, ".")] == '\0') {
if (is_dots(striptarget)) {
bump("security violation: remote host attempted to write to"
" a '.' or '..' path!");
}
@ -1319,7 +1825,7 @@ static void toremote(int argc, char *argv[])
do_cmd(host, user, cmd);
sfree(cmd);
(void) response();
scp_source_setup(targ, targetshouldbedirectory);
for (i = 0; i < argc - 1; i++) {
char *srcpath, *last;
@ -1431,6 +1937,8 @@ static void tolocal(int argc, char *argv[])
do_cmd(host, user, cmd);
sfree(cmd);
scp_sink_setup(src, preserve, recursive);
sink(targ, src);
}
@ -1486,8 +1994,12 @@ static void get_dir_list(int argc, char *argv[])
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);
}
}
/*

View File

@ -287,6 +287,7 @@ void load_settings(char *section, int do_host, Config * cfg)
cfg->ssh_subsys = 0; /* FIXME: load this properly */
cfg->remote_cmd_ptr = cfg->remote_cmd;
cfg->remote_cmd_ptr2 = NULL;
gpps(sesskey, "HostName", "", cfg->host, sizeof(cfg->host));
gppi(sesskey, "PortNumber", default_port, &cfg->port);

23
sftp.c
View File

@ -676,6 +676,29 @@ int fxp_setstat(char *fname, struct fxp_attrs attrs)
}
return 1;
}
int fxp_fsetstat(struct fxp_handle *handle, struct fxp_attrs attrs)
{
struct sftp_packet *pktin, *pktout;
int id;
pktout = sftp_pkt_init(SSH_FXP_FSETSTAT);
sftp_pkt_adduint32(pktout, 0x678); /* request id */
sftp_pkt_addstring_start(pktout);
sftp_pkt_addstring_data(pktout, handle->hstring, handle->hlen);
sftp_pkt_addattrs(pktout, attrs);
sftp_send(pktout);
pktin = sftp_recv();
id = sftp_pkt_getuint32(pktin);
if (id != 0x678) {
fxp_internal_error("request ID mismatch\n");
return 0;
}
id = fxp_got_status(pktin);
if (id != 1) {
return 0;
}
return 1;
}
/*
* Read from a file. Returns the number of bytes read, or -1 on an

1
sftp.h
View File

@ -151,6 +151,7 @@ int fxp_fstat(struct fxp_handle *handle, struct fxp_attrs *attrs);
* Set file attributes.
*/
int fxp_setstat(char *fname, struct fxp_attrs attrs);
int fxp_fsetstat(struct fxp_handle *handle, struct fxp_attrs attrs);
/*
* Read from a file.

127
ssh.c
View File

@ -428,6 +428,13 @@ static struct Packet pktout = { 0, 0, NULL, NULL, 0 };
static unsigned char *deferred_send_data = NULL;
static int deferred_len = 0, deferred_size = 0;
/*
* Gross hack: pscp will try to start SFTP but fall back to scp1 if
* that fails. This variable is the means by which scp.c can reach
* into the SSH code and find out which one it got.
*/
int ssh_fallback_cmd = 0;
static int ssh_version;
static int ssh1_throttle_count;
static int ssh_overall_bufsize;
@ -2663,12 +2670,26 @@ static void ssh1_protocol(unsigned char *in, int inlen, int ispkt)
zlib_decompress_init();
}
if (*cfg.remote_cmd_ptr)
send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cfg.remote_cmd_ptr,
PKT_END);
else
send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
logevent("Started session");
/*
* Start the shell or command.
*
* Special case: if the first-choice command is an SSH2
* subsystem (hence not usable here) and the second choice
* exists, we fall straight back to that.
*/
{
char *cmd = cfg.remote_cmd_ptr;
if (cfg.ssh_subsys && cfg.remote_cmd_ptr2) {
cmd = cfg.remote_cmd_ptr2;
ssh_fallback_cmd = TRUE;
}
if (*cmd)
send_packet(SSH1_CMSG_EXEC_CMD, PKT_STR, cmd, PKT_END);
else
send_packet(SSH1_CMSG_EXEC_SHELL, PKT_END);
logevent("Started session");
}
ssh_state = SSH_STATE_SESSION;
if (size_needed)
@ -4492,43 +4513,70 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
}
/*
* Start a shell or a remote command.
* Start a shell or a remote command. We may have to attempt
* this twice if the config data has provided a second choice
* of command.
*/
ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
ssh2_pkt_adduint32(mainchan->remoteid); /* recipient channel */
if (cfg.ssh_subsys) {
ssh2_pkt_addstring("subsystem");
ssh2_pkt_addbool(1); /* want reply */
ssh2_pkt_addstring(cfg.remote_cmd_ptr);
} else if (*cfg.remote_cmd_ptr) {
ssh2_pkt_addstring("exec");
ssh2_pkt_addbool(1); /* want reply */
ssh2_pkt_addstring(cfg.remote_cmd_ptr);
} else {
ssh2_pkt_addstring("shell");
ssh2_pkt_addbool(1); /* want reply */
}
ssh2_pkt_send();
do {
crWaitUntilV(ispkt);
if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
unsigned i = ssh2_pkt_getuint32();
struct ssh_channel *c;
c = find234(ssh_channels, &i, ssh_channelfind);
if (!c)
continue; /* nonexistent channel */
c->v.v2.remwindow += ssh2_pkt_getuint32();
while (1) {
int subsys;
char *cmd;
if (ssh_fallback_cmd) {
subsys = cfg.ssh_subsys2;
cmd = cfg.remote_cmd_ptr2;
} else {
subsys = cfg.ssh_subsys;
cmd = cfg.remote_cmd_ptr;
}
} while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
bombout(("Server got confused by shell/command request"));
ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
ssh2_pkt_adduint32(mainchan->remoteid); /* recipient channel */
if (subsys) {
ssh2_pkt_addstring("subsystem");
ssh2_pkt_addbool(1); /* want reply */
ssh2_pkt_addstring(cmd);
} else if (*cmd) {
ssh2_pkt_addstring("exec");
ssh2_pkt_addbool(1); /* want reply */
ssh2_pkt_addstring(cmd);
} else {
ssh2_pkt_addstring("shell");
ssh2_pkt_addbool(1); /* want reply */
}
ssh2_pkt_send();
do {
crWaitUntilV(ispkt);
if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
unsigned i = ssh2_pkt_getuint32();
struct ssh_channel *c;
c = find234(ssh_channels, &i, ssh_channelfind);
if (!c)
continue; /* nonexistent channel */
c->v.v2.remwindow += ssh2_pkt_getuint32();
}
} while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
bombout(("Server got confused by shell/command request"));
crReturnV;
}
/*
* We failed to start the command. If this is the
* fallback command, we really are finished; if it's
* not, and if the fallback command exists, try falling
* back to it before complaining.
*/
if (!ssh_fallback_cmd && cfg.remote_cmd_ptr2 != NULL) {
logevent("Primary command failed; attempting fallback");
ssh_fallback_cmd = TRUE;
continue;
}
bombout(("Server refused to start a shell/command"));
crReturnV;
} else {
logevent("Started a shell/command");
}
bombout(("Server refused to start a shell/command"));
crReturnV;
} else {
logevent("Started a shell/command");
break;
}
ssh_state = SSH_STATE_SESSION;
@ -4882,6 +4930,7 @@ static char *ssh_init(char *host, int port, char **realhost)
ssh_echoing = 0;
ssh1_throttle_count = 0;
ssh_overall_bufsize = 0;
ssh_fallback_cmd = 0;
p = connect_to_host(host, port, realhost);
if (p != NULL)

7
ssh.h
View File

@ -195,6 +195,13 @@ extern const struct ssh_mac ssh_sha1_buggy;
*/
extern char sshver[];
/*
* Gross hack: pscp will try to start SFTP but fall back to scp1 if
* that fails. This variable is the means by which scp.c can reach
* into the SSH code and find out which one it got.
*/
extern int ssh_fallback_cmd;
#ifndef MSCRYPTOAPI
void SHATransform(word32 * digest, word32 * data);
#endif