📄 pscp.c
字号:
/*
* scp.c - Scp (Secure Copy) client for PuTTY.
* Joris van Rantwijk, Simon Tatham
*
* This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen.
* They, in turn, used stuff from BSD rcp.
*
* (SGT, 2001-09-10: Joris van Rantwijk assures me that although
* this file as originally submitted was inspired by, and
* _structurally_ based on, ssh-1.2.26's scp.c, there wasn't any
* actual code duplicated, so the above comment shouldn't give rise
* to licensing issues.)
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <assert.h>
#define PUTTY_DO_GLOBALS
#include "putty.h"
#include "psftp.h"
#include "ssh.h"
#include "sftp.h"
#include "storage.h"
#include "int64.h"
static int list = 0;
static int verbose = 0;
static int recursive = 0;
static int preserve = 0;
static int targetshouldbedirectory = 0;
static int statistics = 1;
static int prev_stats_len = 0;
static int scp_unsafe_mode = 0;
static int errs = 0;
static int try_scp = 1;
static int try_sftp = 1;
static int main_cmd_is_sftp = 0;
static int fallback_cmd_is_sftp = 0;
static int using_sftp = 0;
static Backend *back;
static void *backhandle;
static Config cfg;
static void source(char *src);
static void rsource(char *src);
static void sink(char *targ, char *src);
/*
* The maximum amount of queued data we accept before we stop and
* wait for the server to process some.
*/
#define MAX_SCP_BUFSIZE 16384
void ldisc_send(void *handle, char *buf, int len, int interactive)
{
/*
* This is only here because of the calls to ldisc_send(NULL,
* 0) in ssh.c. Nothing in PSCP actually needs to use the ldisc
* as an ldisc. So if we get called with any real data, I want
* to know about it.
*/
assert(len == 0);
}
static void tell_char(FILE * stream, char c)
{
fputc(c, stream);
}
static void tell_str(FILE * stream, char *str)
{
unsigned int i;
for (i = 0; i < strlen(str); ++i)
tell_char(stream, str[i]);
}
static void tell_user(FILE * stream, char *fmt, ...)
{
char *str, *str2;
va_list ap;
va_start(ap, fmt);
str = dupvprintf(fmt, ap);
va_end(ap);
str2 = dupcat(str, "\n", NULL);
sfree(str);
tell_str(stream, str2);
sfree(str2);
}
/*
* Print an error message and perform a fatal exit.
*/
void fatalbox(char *fmt, ...)
{
char *str, *str2;
va_list ap;
va_start(ap, fmt);
str = dupvprintf(fmt, ap);
str2 = dupcat("Fatal: ", str, "\n", NULL);
sfree(str);
va_end(ap);
tell_str(stderr, str2);
sfree(str2);
errs++;
cleanup_exit(1);
}
void modalfatalbox(char *fmt, ...)
{
char *str, *str2;
va_list ap;
va_start(ap, fmt);
str = dupvprintf(fmt, ap);
str2 = dupcat("Fatal: ", str, "\n", NULL);
sfree(str);
va_end(ap);
tell_str(stderr, str2);
sfree(str2);
errs++;
cleanup_exit(1);
}
void connection_fatal(void *frontend, char *fmt, ...)
{
char *str, *str2;
va_list ap;
va_start(ap, fmt);
str = dupvprintf(fmt, ap);
str2 = dupcat("Fatal: ", str, "\n", NULL);
sfree(str);
va_end(ap);
tell_str(stderr, str2);
sfree(str2);
errs++;
cleanup_exit(1);
}
/*
* In pscp, all agent requests should be synchronous, so this is a
* never-called stub.
*/
void agent_schedule_callback(void (*callback)(void *, void *, int),
void *callback_ctx, void *data, int len)
{
assert(!"We shouldn't be here");
}
/*
* Receive a block of data from the SSH link. Block until all data
* is available.
*
* To do this, we repeatedly call the SSH protocol module, with our
* own trap in from_backend() to catch the data that comes back. We
* do this until we have enough data.
*/
static unsigned char *outptr; /* where to put the data */
static unsigned outlen; /* how much data required */
static unsigned char *pending = NULL; /* any spare data */
static unsigned pendlen = 0, pendsize = 0; /* length and phys. size of buffer */
int from_backend(void *frontend, int is_stderr, const char *data, int datalen)
{
unsigned char *p = (unsigned char *) data;
unsigned len = (unsigned) datalen;
/*
* stderr data is just spouted to local stderr and otherwise
* ignored.
*/
if (is_stderr) {
if (len > 0)
fwrite(data, 1, len, stderr);
return 0;
}
/*
* If this is before the real session begins, just return.
*/
if (!outptr)
return 0;
if ((outlen > 0) && (len > 0)) {
unsigned used = outlen;
if (used > len)
used = len;
memcpy(outptr, p, used);
outptr += used;
outlen -= used;
p += used;
len -= used;
}
if (len > 0) {
if (pendsize < pendlen + len) {
pendsize = pendlen + len + 4096;
pending = sresize(pending, pendsize, unsigned char);
}
memcpy(pending + pendlen, p, len);
pendlen += len;
}
return 0;
}
int from_backend_untrusted(void *frontend_handle, const char *data, int len)
{
/*
* No "untrusted" output should get here (the way the code is
* currently, it's all diverted by FLAG_STDERR).
*/
assert(!"Unexpected call to from_backend_untrusted()");
return 0; /* not reached */
}
static int ssh_scp_recv(unsigned char *buf, int len)
{
outptr = buf;
outlen = len;
/*
* See if the pending-input block contains some of what we
* need.
*/
if (pendlen > 0) {
unsigned pendused = pendlen;
if (pendused > outlen)
pendused = outlen;
memcpy(outptr, pending, pendused);
memmove(pending, pending + pendused, pendlen - pendused);
outptr += pendused;
outlen -= pendused;
pendlen -= pendused;
if (pendlen == 0) {
pendsize = 0;
sfree(pending);
pending = NULL;
}
if (outlen == 0)
return len;
}
while (outlen > 0) {
if (back->exitcode(backhandle) >= 0 || ssh_sftp_loop_iteration() < 0)
return 0; /* doom */
}
return len;
}
/*
* Loop through the ssh connection and authentication process.
*/
static void ssh_scp_init(void)
{
while (!back->sendok(backhandle)) {
if (ssh_sftp_loop_iteration() < 0)
return; /* doom */
}
/* Work out which backend we ended up using. */
if (!ssh_fallback_cmd(backhandle))
using_sftp = main_cmd_is_sftp;
else
using_sftp = fallback_cmd_is_sftp;
if (verbose) {
if (using_sftp)
tell_user(stderr, "Using SFTP");
else
tell_user(stderr, "Using SCP1");
}
}
/*
* Print an error message and exit after closing the SSH link.
*/
static void bump(char *fmt, ...)
{
char *str, *str2;
va_list ap;
va_start(ap, fmt);
str = dupvprintf(fmt, ap);
va_end(ap);
str2 = dupcat(str, "\n", NULL);
sfree(str);
tell_str(stderr, str2);
sfree(str2);
errs++;
if (back != NULL && back->connected(backhandle)) {
char ch;
back->special(backhandle, TS_EOF);
ssh_scp_recv((unsigned char *) &ch, 1);
}
cleanup_exit(1);
}
/*
* Open an SSH connection to user@host and execute cmd.
*/
static void do_cmd(char *host, char *user, char *cmd)
{
const char *err;
char *realhost;
void *logctx;
if (host == NULL || host[0] == '\0')
bump("Empty host name");
/*
* Remove fiddly bits of address: remove a colon suffix, and
* the square brackets around an IPv6 literal address.
*/
if (host[0] == '[') {
host++;
host[strcspn(host, "]")] = '\0';
} else {
host[strcspn(host, ":")] = '\0';
}
/*
* If we haven't loaded session details already (e.g., from -load),
* try looking for a session called "host".
*/
if (!loaded_session) {
/* Try to load settings for `host' into a temporary config */
Config cfg2;
cfg2.host[0] = '\0';
do_defaults(host, &cfg2);
if (cfg2.host[0] != '\0') {
/* Settings present and include hostname */
/* Re-load data into the real config. */
do_defaults(host, &cfg);
} else {
/* Session doesn't exist or mention a hostname. */
/* Use `host' as a bare hostname. */
strncpy(cfg.host, host, sizeof(cfg.host) - 1);
cfg.host[sizeof(cfg.host) - 1] = '\0';
}
} else {
/* Patch in hostname `host' to session details. */
strncpy(cfg.host, host, sizeof(cfg.host) - 1);
cfg.host[sizeof(cfg.host) - 1] = '\0';
}
/*
* Force use of SSH. (If they got the protocol wrong we assume the
* port is useless too.)
*/
if (cfg.protocol != PROT_SSH) {
cfg.protocol = PROT_SSH;
cfg.port = 22;
}
/*
* Enact command-line overrides.
*/
cmdline_run_saved(&cfg);
/*
* Trim leading whitespace off the hostname if it's there.
*/
{
int space = strspn(cfg.host, " \t");
memmove(cfg.host, cfg.host+space, 1+strlen(cfg.host)-space);
}
/* See if host is of the form user@host */
if (cfg.host[0] != '\0') {
char *atsign = strrchr(cfg.host, '@');
/* Make sure we're not overflowing the user field */
if (atsign) {
if (atsign - cfg.host < sizeof cfg.username) {
strncpy(cfg.username, cfg.host, atsign - cfg.host);
cfg.username[atsign - cfg.host] = '\0';
}
memmove(cfg.host, atsign + 1, 1 + strlen(atsign + 1));
}
}
/*
* Remove any remaining whitespace from the hostname.
*/
{
int p1 = 0, p2 = 0;
while (cfg.host[p2] != '\0') {
if (cfg.host[p2] != ' ' && cfg.host[p2] != '\t') {
cfg.host[p1] = cfg.host[p2];
p1++;
}
p2++;
}
cfg.host[p1] = '\0';
}
/* Set username */
if (user != NULL && user[0] != '\0') {
strncpy(cfg.username, user, sizeof(cfg.username) - 1);
cfg.username[sizeof(cfg.username) - 1] = '\0';
} else if (cfg.username[0] == '\0') {
user = get_username();
if (!user)
bump("Empty user name");
else {
if (verbose)
tell_user(stderr, "Guessing user name: %s", user);
strncpy(cfg.username, user, sizeof(cfg.username) - 1);
cfg.username[sizeof(cfg.username) - 1] = '\0';
sfree(user);
}
}
/*
* Disable scary things which shouldn't be enabled for simple
* things like SCP and SFTP: agent forwarding, port forwarding,
* X forwarding.
*/
cfg.x11_forward = 0;
cfg.agentfwd = 0;
cfg.portfwd[0] = cfg.portfwd[1] = '\0';
/*
* Set up main and possibly fallback command depending on
* options specified by user.
* Attempt to start the SFTP subsystem as a first choice,
* falling back to the provided scp command if that fails.
*/
cfg.remote_cmd_ptr2 = NULL;
if (try_sftp) {
/* First choice is SFTP subsystem. */
main_cmd_is_sftp = 1;
strcpy(cfg.remote_cmd, "sftp");
cfg.ssh_subsys = TRUE;
if (try_scp) {
/* Fallback is to use the provided scp command. */
fallback_cmd_is_sftp = 0;
cfg.remote_cmd_ptr2 = cmd;
cfg.ssh_subsys2 = FALSE;
} else {
/* Since we're not going to try SCP, we may as well try
* harder to find an SFTP server, since in the current
* implementation we have a spare slot. */
fallback_cmd_is_sftp = 1;
/* see psftp.c for full explanation of this kludge */
cfg.remote_cmd_ptr2 =
"test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n"
"test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n"
"exec sftp-server";
cfg.ssh_subsys2 = FALSE;
}
} else {
/* Don't try SFTP at all; just try the scp command. */
main_cmd_is_sftp = 0;
cfg.remote_cmd_ptr = cmd;
cfg.ssh_subsys = FALSE;
}
cfg.nopty = TRUE;
back = &ssh_backend;
err = back->init(NULL, &backhandle, &cfg, cfg.host, cfg.port, &realhost,
0, cfg.tcp_keepalives);
if (err != NULL)
bump("ssh_init: %s", err);
logctx = log_init(NULL, &cfg);
back->provide_logctx(backhandle, logctx);
console_provide_logctx(logctx);
ssh_scp_init();
if (verbose && realhost != NULL)
tell_user(stderr, "Connected to %s\n", realhost);
sfree(realhost);
}
/*
* Update statistic information about current file.
*/
static void print_stats(char *name, uint64 size, uint64 done,
time_t start, time_t now)
{
float ratebs;
unsigned long eta;
char *etastr;
int pct;
int len;
int elap;
double donedbl;
double sizedbl;
elap = (unsigned long) difftime(now, start);
if (now > start)
ratebs = (float) (uint64_to_double(done) / elap);
else
ratebs = (float) uint64_to_double(done);
if (ratebs < 1.0)
eta = (unsigned long) (uint64_to_double(uint64_subtract(size, done)));
else {
eta = (unsigned long)
((uint64_to_double(uint64_subtract(size, done)) / ratebs));
}
etastr = dupprintf("%02ld:%02ld:%02ld",
eta / 3600, (eta % 3600) / 60, eta % 60);
donedbl = uint64_to_double(done);
sizedbl = uint64_to_double(size);
pct = (int) (100 * (donedbl * 1.0 / sizedbl));
{
char donekb[40];
/* divide by 1024 to provide kB */
uint64_decimal(uint64_shift_right(done, 10), donekb);
len = printf("\r%-25.25s | %s kB | %5.1f kB/s | ETA: %8s | %3d%%",
name,
donekb, ratebs / 1024.0, etastr, pct);
if (len < prev_stats_len)
printf("%*s", prev_stats_len - len, "");
prev_stats_len = len;
if (uint64_compare(done, size) == 0)
printf("\n");
fflush(stdout);
}
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[0] != '[' && str[1] == ':'))
return (NULL);
while (*str != '\0' && *str != ':' && *str != '/' && *str != '\\') {
if (*str == '[') {
/* Skip over IPv6 literal addresses
* (eg: 'jeroen@[2001:db8::1]:myfile.txt') */
char *ipv6_end = strchr(str, ']');
if (ipv6_end) {
str = ipv6_end;
}
}
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;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -