📄 scp.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"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 gui_mode = 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 const 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 16384void 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){ if (!gui_mode) fputc(c, stream); else gui_send_char(stream == stderr, c);}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++; if (gui_mode) gui_send_errcount(list, 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++; if (gui_mode) gui_send_errcount(list, 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++; if (gui_mode) gui_send_errcount(list, 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); if (!pending) fatalbox("Out of memory"); } memcpy(pending + pendlen, p, len); pendlen += len; } return 0;}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 (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->socket(backhandle) != NULL) { char ch; back->special(backhandle, TS_EOF); ssh_scp_recv((unsigned char *) &ch, 1); } if (gui_mode) gui_send_errcount(list, errs); 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"); /* * 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)); } } /* * Trim a colon suffix off the hostname if it's there. */ cfg.host[strcspn(cfg.host, ":")] = '\0'; /* * 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, unsigned long size, unsigned long done, time_t start, time_t now){ float ratebs; unsigned long eta; char *etastr; int pct; int len; int elap; elap = (unsigned long) difftime(now, start); if (now > start) ratebs = (float) done / elap; else ratebs = (float) done; if (ratebs < 1.0) eta = size - done; else eta = (unsigned long) ((size - done) / ratebs); etastr = dupprintf("%02ld:%02ld:%02ld", eta / 3600, (eta % 3600) / 60, eta % 60); pct = (int) (100 * (done * 1.0 / size)); if (gui_mode) { gui_update_stats(name, size, pct, elap, done, eta, (unsigned long) ratebs); } else { len = printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%", name, done / 1024, ratebs / 1024.0, etastr, pct); if (len < prev_stats_len) printf("%*s", prev_stats_len - len, ""); prev_stats_len = len; if (done == size) 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[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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -