From 07d9aa1362c646e768b89ad166b6c0881f7ac74b Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 31 Aug 1999 09:20:48 +0000 Subject: [PATCH] Added Joris van Rantwijk's scp client git-svn-id: svn://svn.tartarus.org/sgt/putty@205 cda61777-01e9-0310-a592-d414129be87e --- scp.c | 806 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ scp.h | 21 ++ scp.ico | Bin 0 -> 1086 bytes scp.rc | 2 + scpssh.c | 566 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1395 insertions(+) create mode 100644 scp.c create mode 100644 scp.h create mode 100644 scp.ico create mode 100644 scp.rc create mode 100644 scpssh.c diff --git a/scp.c b/scp.c new file mode 100644 index 00000000..2892db23 --- /dev/null +++ b/scp.c @@ -0,0 +1,806 @@ +/* + * scp.c - Scp (Secure Copy) client for PuTTY. + * Joris van Rantwijk, Aug 1999. + * + * This is mainly based on ssh-1.2.26/scp.c by Timo Rinne & Tatu Ylonen. + * They, in turn, used stuff from BSD rcp. + */ + + +#include +#include +#include +#include +#include +#include + +#define PUTTY_DO_GLOBALS +#include "putty.h" +#include "scp.h" + + +#define TIME_POSIX_TO_WIN(t, ft) (*(LONGLONG*)&(ft) = \ + ((LONGLONG) (t) + (LONGLONG) 11644473600) * (LONGLONG) 10000000) +#define TIME_WIN_TO_POSIX(ft, t) ((t) = (unsigned long) \ + ((*(LONGLONG*)&(ft)) / (LONGLONG) 10000000 - (LONGLONG) 11644473600)) + + +int verbose = 0; +static int recursive = 0; +static int preserve = 0; +static int targetshouldbedirectory = 0; +static int statistics = 1; +static int errs = 0; +static int connection_open = 0; + +static void source(char *src); +static void rsource(char *src); +static void sink(char *targ); + + +/* + * Print an error message and perform a fatal exit. + */ +void fatalbox(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "Fatal: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} + + +/* + * Print an error message and exit after closing the SSH link. + */ +static void bump(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "Fatal: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + if (connection_open) { + char ch; + ssh_send_eof(); + ssh_recv(&ch, 1); + } + exit(1); +} + + +void ssh_get_password(char *prompt, char *str, int maxlen) +{ + HANDLE hin, hout; + DWORD savemode, i; + + hin = GetStdHandle(STD_INPUT_HANDLE); + hout = GetStdHandle(STD_OUTPUT_HANDLE); + if (hin == INVALID_HANDLE_VALUE || hout == INVALID_HANDLE_VALUE) + bump("Cannot get standard input/output handles"); + + GetConsoleMode(hin, &savemode); + SetConsoleMode(hin, (savemode & (~ENABLE_ECHO_INPUT)) | + ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT); + + WriteFile(hout, prompt, strlen(prompt), &i, NULL); + ReadFile(hin, str, maxlen-1, &i, NULL); + + SetConsoleMode(hin, savemode); + + if (i > maxlen) i = maxlen-1; else i = i - 2; + str[i] = '\0'; + + WriteFile(hout, "\r\n", 2, &i, NULL); +} + + +/* + * Open an SSH connection to user@host and execute cmd. + */ +static void do_cmd(char *host, char *user, char *cmd) +{ + char *err, *realhost; + + if (host == NULL || host[0] == '\0') + bump("Empty host name"); + + /* Try to load settings for this host */ + do_defaults(host); + if (cfg.host[0] == '\0') { + /* No settings for this host; use defaults */ + strncpy(cfg.host, host, sizeof(cfg.host)-1); + cfg.host[sizeof(cfg.host)-1] = '\0'; + cfg.port = 22; + } + + /* Set username */ + if (user != NULL && user[0] != '\0') { + strncpy(cfg.username, user, sizeof(cfg.username)-1); + cfg.username[sizeof(cfg.username)-1] = '\0'; + cfg.port = 22; + } else if (cfg.username[0] == '\0') { + bump("Empty user name"); + } + + if (cfg.protocol != PROT_SSH) + cfg.port = 22; + + err = ssh_init(cfg.host, cfg.port, cmd, &realhost); + if (err != NULL) + bump("ssh_init: %s", err); + if (verbose && realhost != NULL) + fprintf(stderr, "Connected to %s\n", realhost); + + connection_open = 1; +} + + +/* + * Update statistic information about current file. + */ +static void print_stats(char *name, unsigned long size, unsigned long done, + unsigned long start, unsigned long now) +{ + float ratebs; + unsigned long eta; + char etastr[10]; + int pct; + + if (now > start) + ratebs = (float) done / (now - start); + else + ratebs = (float) done; + + if (ratebs < 1.0) + eta = size - done; + else + eta = (size - done) / ratebs; + sprintf(etastr, "%02d:%02d:%02d", + eta / 3600, (eta % 3600) / 60, eta % 60); + + pct = 100.0 * (float) done / size; + + printf("\r%-25.25s | %10ld kB | %5.1f kB/s | ETA: %8s | %3d%%", + name, done / 1024, ratebs / 1024.0, + etastr, pct); + + if (done == size) + printf("\n"); +} + + +/* + * Find a colon in str and return a pointer to the colon. + * This is used to seperate 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); +} + + +/* + * Wait for a response from the other side. + * Return 0 if ok, -1 if error. + */ +static int response(void) +{ + char ch, resp, rbuf[2048]; + int p; + + if (ssh_recv(&resp, 1) <= 0) + bump("Lost connection"); + + p = 0; + switch (resp) { + case 0: /* ok */ + return (0); + default: + rbuf[p++] = resp; + /* fallthrough */ + case 1: /* error */ + case 2: /* fatal error */ + do { + if (ssh_recv(&ch, 1) <= 0) + bump("Protocol error: Lost connection"); + rbuf[p++] = ch; + } while (p < sizeof(rbuf) && ch != '\n'); + rbuf[p-1] = '\0'; + if (resp == 1) + fprintf(stderr, "%s\n", rbuf); + else + bump("%s", rbuf); + errs++; + return (-1); + } +} + + +/* + * Send an error message to the other side and to the screen. + * Increment error counter. + */ +static void run_err(const char *fmt, ...) +{ + char str[2048]; + va_list ap; + va_start(ap, fmt); + errs++; + strcpy(str, "\01scp: "); + vsprintf(str+strlen(str), fmt, ap); + strcat(str, "\n"); + ssh_send(str, strlen(str)); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); +} + + +/* + * Execute the source part of the SCP protocol. + */ +static void source(char *src) +{ + char buf[2048]; + unsigned long size; + char *last; + HANDLE f; + DWORD attr; + unsigned long i; + unsigned long stat_bytes; + unsigned long stat_starttime, stat_lasttime; + + attr = GetFileAttributes(src); + if (attr == -1) { + run_err("%s: No such file or directory", src); + return; + } + + if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { + if (recursive) + rsource(src); + else + run_err("%s: not a regular file", src); + return; + } + + if ((last = strrchr(src, '/')) == NULL) + last = src; + else + last++; + if (strrchr(last, '\\') != NULL) + last = strrchr(last, '\\') + 1; + if (last == src && strchr(src, ':') != NULL) + last = strchr(src, ':') + 1; + + f = CreateFile(src, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, 0, 0); + if (f == INVALID_HANDLE_VALUE) { + run_err("%s: Cannot open file"); + return; + } + + if (preserve) { + FILETIME actime, wrtime; + unsigned long mtime, atime; + GetFileTime(f, NULL, &actime, &wrtime); + TIME_WIN_TO_POSIX(actime, atime); + TIME_WIN_TO_POSIX(wrtime, mtime); + sprintf(buf, "T%lu 0 %lu 0\n", mtime, atime); + ssh_send(buf, strlen(buf)); + if (response()) + return; + } + + size = GetFileSize(f, NULL); + sprintf(buf, "C0644 %lu %s\n", size, last); + if (verbose) + fprintf(stderr, "Sending file modes: %s", buf); + ssh_send(buf, strlen(buf)); + if (response()) + return; + + if (statistics) { + stat_bytes = 0; + stat_starttime = time(NULL); + stat_lasttime = 0; + } + + for (i = 0; i < size; i += 4096) { + char transbuf[4096]; + DWORD j, k = 4096; + if (i + k > size) k = size - i; + if (! ReadFile(f, transbuf, k, &j, NULL) || j != k) { + if (statistics) printf("\n"); + bump("%s: Read error", src); + } + ssh_send(transbuf, k); + if (statistics) { + stat_bytes += k; + if (time(NULL) != stat_lasttime || + i + k == size) { + stat_lasttime = time(NULL); + print_stats(last, size, stat_bytes, + stat_starttime, stat_lasttime); + } + } + } + CloseHandle(f); + + ssh_send("", 1); + (void) response(); +} + + +/* + * Recursively send the contents of a directory. + */ +static void rsource(char *src) +{ + char buf[2048]; + char *last; + HANDLE dir; + WIN32_FIND_DATA fdat; + int ok; + + if ((last = strrchr(src, '/')) == NULL) + last = src; + else + last++; + if (strrchr(last, '\\') != NULL) + last = strrchr(last, '\\') + 1; + if (last == src && strchr(src, ':') != NULL) + last = strchr(src, ':') + 1; + + /* maybe send filetime */ + + sprintf(buf, "D0755 0 %s\n", last); + if (verbose) + fprintf(stderr, "Entering directory: %s", buf); + ssh_send(buf, strlen(buf)); + if (response()) + return; + + sprintf(buf, "%s/*", src); + dir = FindFirstFile(buf, &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); + } else { + sprintf(buf, "%s/%s", src, fdat.cFileName); + source(buf); + } + ok = FindNextFile(dir, &fdat); + } + FindClose(dir); + + sprintf(buf, "E\n"); + ssh_send(buf, strlen(buf)); + (void) response(); +} + + +/* + * Execute the sink part of the SCP protocol. + */ +static void sink(char *targ) +{ + char buf[2048]; + char namebuf[2048]; + char ch; + int targisdir = 0; + int settime = 0; + int exists; + DWORD attr; + HANDLE f; + unsigned long mtime, atime; + unsigned int mode; + unsigned long size, i; + int wrerror = 0; + unsigned long stat_bytes; + unsigned long stat_starttime, stat_lasttime; + char *stat_name; + + attr = GetFileAttributes(targ); + if (attr != -1 && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + targisdir = 1; + + if (targetshouldbedirectory && !targisdir) + bump("%s: Not a directory", targ); + + ssh_send("", 1); + while (1) { + settime = 0; +gottime: + if (ssh_recv(&ch, 1) <= 0) + return; + if (ch == '\n') + bump("Protocol error: Unexpected newline"); + i = 0; + buf[i++] = ch; + do { + if (ssh_recv(&ch, 1) <= 0) + bump("Lost connection"); + buf[i++] = ch; + } while (i < sizeof(buf) && ch != '\n'); + buf[i-1] = '\0'; + switch (buf[0]) { + case '\01': /* error */ + fprintf(stderr, "%s\n", buf+1); + errs++; + continue; + case '\02': /* fatal error */ + bump("%s", buf+1); + case 'E': + ssh_send("", 1); + return; + case 'T': + if (sscanf(buf, "T%d %*d %d %*d", + &mtime, &atime) == 2) { + settime = 1; + ssh_send("", 1); + goto gottime; + } + bump("Protocol error: Illegal time format"); + case 'C': + case 'D': + break; + default: + bump("Protocol error: Expected control record"); + } + + if (sscanf(buf+1, "%u %u %[^\n]", &mode, &size, namebuf) != 3) + bump("Protocol error: Illegal file descriptor format"); + if (targisdir) { + char t[2048]; + strcpy(t, targ); + if (targ[0] != '\0') + strcat(t, "/"); + strcat(t, namebuf); + strcpy(namebuf, t); + } else { + strcpy(namebuf, targ); + } + attr = GetFileAttributes(namebuf); + exists = (attr != -1); + + if (buf[0] == 'D') { + if (exists && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0) { + run_err("%s: Not a directory", namebuf); + continue; + } + if (!exists) { + if (! CreateDirectory(namebuf, NULL)) { + run_err("%s: Cannot create directory", + namebuf); + continue; + } + } + sink(namebuf); + /* can we set the timestamp for directories ? */ + continue; + } + + f = CreateFile(namebuf, GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); + if (f == INVALID_HANDLE_VALUE) { + run_err("%s: Cannot create file", namebuf); + continue; + } + + ssh_send("", 1); + + if (statistics) { + 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; + } + + for (i = 0; i < size; i += 4096) { + char transbuf[4096]; + int j, k = 4096; + if (i + k > size) k = size - i; + if (ssh_recv(transbuf, k) == 0) + bump("Lost connection"); + if (wrerror) continue; + if (! WriteFile(f, transbuf, k, &j, NULL) || j != k) { + wrerror = 1; + if (statistics) + printf("\r%-25.25s | %50s\n", + stat_name, + "Write error.. waiting for end of file"); + continue; + } + if (statistics) { + stat_bytes += k; + if (time(NULL) > stat_lasttime || + i + k == size) { + stat_lasttime = time(NULL); + print_stats(stat_name, size, stat_bytes, + stat_starttime, stat_lasttime); + } + } + } + (void) response(); + + if (settime) { + FILETIME actime, wrtime; + TIME_POSIX_TO_WIN(atime, actime); + TIME_POSIX_TO_WIN(mtime, wrtime); + SetFileTime(f, NULL, &actime, &wrtime); + } + + CloseHandle(f); + if (wrerror) { + run_err("%s: Write error", namebuf); + continue; + } + ssh_send("", 1); + } +} + + +/* + * We will copy local files to a remote server. + */ +static void toremote(int argc, char *argv[]) +{ + char *src, *targ, *host, *user; + char *cmd; + int i; + + targ = argv[argc-1]; + + /* Seperate host from filename */ + host = targ; + targ = colon(targ); + if (targ == NULL) + bump("targ == NULL in toremote()"); + *targ++ = '\0'; + if (*targ == '\0') + targ = "."; + /* Substitute "." for emtpy target */ + + /* Seperate host and username */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + if (argc == 2) { + /* Find out if the source filespec covers multiple files + if so, we should set the targetshouldbedirectory flag */ + HANDLE fh; + WIN32_FIND_DATA fdat; + if (colon(argv[0]) != NULL) + bump("%s: Remote to remote not supported", argv[0]); + fh = FindFirstFile(argv[0], &fdat); + if (fh == INVALID_HANDLE_VALUE) + bump("%s: No such file or directory\n", argv[0]); + if (FindNextFile(fh, &fdat)) + targetshouldbedirectory = 1; + FindClose(fh); + } + + cmd = smalloc(strlen(targ) + 100); + sprintf(cmd, "scp%s%s%s%s -t %s", + verbose ? " -v" : "", + recursive ? " -r" : "", + preserve ? " -p" : "", + targetshouldbedirectory ? " -d" : "", + targ); + do_cmd(host, user, cmd); + sfree(cmd); + + (void) response(); + + for (i = 0; i < argc - 1; i++) { + HANDLE dir; + WIN32_FIND_DATA fdat; + src = argv[i]; + if (colon(src) != NULL) { + fprintf(stderr, + "%s: Remote to remote not supported\n", src); + errs++; + continue; + } + 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]; + if (strlen(src) + strlen(fdat.cFileName) >= + sizeof(namebuf)) { + fprintf(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); + } while (FindNextFile(dir, &fdat)); + FindClose(dir); + } +} + + +/* + * We will copy files from a remote server to the local machine. + */ +static void tolocal(int argc, char *argv[]) +{ + char *src, *targ, *host, *user; + char *cmd; + + if (argc != 2) + bump("More than one remote source not supported"); + + src = argv[0]; + targ = argv[1]; + + /* Seperate host from filename */ + host = src; + src = colon(src); + if (src == NULL) + bump("Local to local copy not supported"); + *src++ = '\0'; + if (*src == '\0') + src = "."; + /* Substitute "." for empty filename */ + + /* Seperate username and hostname */ + user = host; + host = strrchr(host, '@'); + if (host == NULL) { + host = user; + user = NULL; + } else { + *host++ = '\0'; + if (*user == '\0') + user = NULL; + } + + cmd = smalloc(strlen(src) + 100); + sprintf(cmd, "scp%s%s%s%s -f %s", + verbose ? " -v" : "", + recursive ? " -r" : "", + preserve ? " -p" : "", + targetshouldbedirectory ? " -d" : "", + src); + do_cmd(host, user, cmd); + sfree(cmd); + + sink(targ); +} + + +/* + * Initialize the Win$ock driver. + */ +static void init_winsock() +{ + WORD winsock_ver; + WSADATA wsadata; + + winsock_ver = MAKEWORD(1, 1); + if (WSAStartup(winsock_ver, &wsadata)) + bump("Unable to initialise WinSock"); + if (LOBYTE(wsadata.wVersion) != 1 || + HIBYTE(wsadata.wVersion) != 1) + bump("WinSock version is incompatible with 1.1"); +} + + +/* + * Short description of parameters. + */ +static void usage() +{ + printf("PuTTY Secure Copy client\n"); + printf("%s\n", ver); + printf("usage: scp [-p] [-q] [-r] [-v] [user@]host:source target\n"); + printf(" scp [-p] [-q] [-r] [-v] source [source..]" + " [user@]host:target\n"); + exit(1); +} + + +/* + * Main program (no, really?) + */ +int main(int argc, char *argv[]) +{ + int i; + + init_winsock(); + + for (i = 1; i < argc; i++) { + if (argv[i][0] != '-') + break; + if (strcmp(argv[i], "-v") == 0) + verbose = 1; + else if (strcmp(argv[i], "-r") == 0) + recursive = 1; + else if (strcmp(argv[i], "-p") == 0) + preserve = 1; + else if (strcmp(argv[i], "-q") == 0) + statistics = 0; + else if (strcmp(argv[i], "-h") == 0 || + strcmp(argv[i], "-?") == 0) + usage(); + else if (strcmp(argv[i], "--") == 0) + { i++; break; } + else + usage(); + } + argc -= i; + argv += i; + + if (argc < 2) + usage(); + if (argc > 2) + targetshouldbedirectory = 1; + + if (colon(argv[argc-1]) != NULL) + toremote(argc, argv); + else + tolocal(argc, argv); + + if (connection_open) { + char ch; + ssh_send_eof(); + ssh_recv(&ch, 1); + } + WSACleanup(); + random_save_seed(); + + return (errs == 0 ? 0 : 1); +} + +/* end */ diff --git a/scp.h b/scp.h new file mode 100644 index 00000000..160e432b --- /dev/null +++ b/scp.h @@ -0,0 +1,21 @@ +/* + * scp.h + * Joris van Rantwijk, Aug 1999. + */ + + +/* + * Exported from scp.c + */ +extern int verbose; +void ssh_get_password(char *prompt, char *str, int maxlen); + + +/* + * Exported from scpssh.c + */ +char * ssh_init(char *host, int port, char *cmd, char **realhost); +int ssh_recv(unsigned char *buf, int len); +void ssh_send(unsigned char *buf, int len); +void ssh_send_eof(void); + diff --git a/scp.ico b/scp.ico new file mode 100644 index 0000000000000000000000000000000000000000..203ece8a36ec92f9cb9ff16eb9468457f478ff69 GIT binary patch literal 1086 zcmb_aF;0X)6n*S&0trMsfCZJ=at~l4-orv~U}6Cs(@EzgJODinRyG{KPD=|5y4_@n z!Pbe8C7Lv62P&Yt0tefR!zoqkQzN=~q&GHS^C;P{G z(Eg-;Q-p_8%S`*l5X|rMJc5*Tb%1o& O+fjFw$-)2SzP +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "scp.h" + +#define SSH_MSG_DISCONNECT 1 +#define SSH_SMSG_PUBLIC_KEY 2 +#define SSH_CMSG_SESSION_KEY 3 +#define SSH_CMSG_USER 4 +#define SSH_CMSG_AUTH_PASSWORD 9 +#define SSH_CMSG_EXEC_CMD 13 +#define SSH_SMSG_SUCCESS 14 +#define SSH_SMSG_FAILURE 15 +#define SSH_CMSG_STDIN_DATA 16 +#define SSH_SMSG_STDOUT_DATA 17 +#define SSH_SMSG_STDERR_DATA 18 +#define SSH_CMSG_EOF 19 +#define SSH_SMSG_EXIT_STATUS 20 +#define SSH_CMSG_EXIT_CONFIRMATION 33 +#define SSH_MSG_DEBUG 36 + +#define GET_32BIT(cp) \ + (((unsigned long)(unsigned char)(cp)[0] << 24) | \ + ((unsigned long)(unsigned char)(cp)[1] << 16) | \ + ((unsigned long)(unsigned char)(cp)[2] << 8) | \ + ((unsigned long)(unsigned char)(cp)[3])) + +#define PUT_32BIT(cp, value) { \ + (cp)[0] = (value) >> 24; \ + (cp)[1] = (value) >> 16; \ + (cp)[2] = (value) >> 8; \ + (cp)[3] = (value); } + +static SOCKET s = INVALID_SOCKET; + +static unsigned char session_key[32]; +static struct ssh_cipher *cipher = NULL; + +static char *savedhost; + +struct Packet { + long length; + int type; + unsigned long crc; + unsigned char *data; + unsigned char *body; + long maxlen; +}; + +static struct Packet pktin = { 0, 0, 0, NULL, 0 }; +static struct Packet pktout = { 0, 0, 0, NULL, 0 }; + + +static void s_write (char *buf, int len) { + while (len > 0) { + int i = send (s, buf, len, 0); + noise_ultralight(i); + if (i <= 0) + fatalbox("Lost connection while sending"); + len -= i, buf += i; + } +} + +static int s_read (char *buf, int len) { + int ret = 0; + while (len > 0) { + int i = recv (s, buf, len, 0); + noise_ultralight(i); + if (i > 0) + len -= i, buf += i, ret += i; + else + return i; + } + return ret; +} + +/* + * Read and decrypt one incoming SSH packet. + */ +static void get_packet() +{ + unsigned char buf[4]; + int ret; + int len, pad, biglen; + +next_packet: + + pktin.type = 0; + pktin.length = 0; + + ret = s_read(buf, 4); + if (ret != 4) { + closesocket(s); + s = INVALID_SOCKET; + return; + } + + len = GET_32BIT(buf); + +#ifdef FWHACK + if (len == 0x52656d6f) { + len = 0x300; + } +#endif + + pad = 8 - (len % 8); + biglen = len + pad; + len -= 5; /* type and CRC */ + + pktin.length = len; + if (pktin.maxlen < biglen) { + pktin.maxlen = biglen; + pktin.data = (pktin.data == NULL) ? + smalloc(biglen) : srealloc(pktin.data, biglen); + } + + ret = s_read(pktin.data, biglen); + if (ret != biglen) { + closesocket(s); + s = INVALID_SOCKET; + return; + } + + if (cipher) + cipher->decrypt(pktin.data, biglen); + + pktin.type = pktin.data[pad]; + pktin.body = pktin.data + pad + 1; + + if (pktin.type == SSH_MSG_DEBUG) { + if (verbose) { + int len = GET_32BIT(pktin.body); + fprintf(stderr, "Remote: "); + fwrite(pktin.body + 4, len, 1, stderr); + fprintf(stderr, "\n"); + } + goto next_packet; + } +} + +static void s_wrpkt_start(int type, int len) { + int pad, biglen; + + len += 5; /* type and CRC */ + pad = 8 - (len%8); + biglen = len + pad; + + pktout.length = len-5; + if (pktout.maxlen < biglen) { + pktout.maxlen = biglen; + pktout.data = (pktout.data == NULL ? malloc(biglen+4) : + realloc(pktout.data, biglen+4)); + if (!pktout.data) + fatalbox("Out of memory"); + } + + pktout.type = type; + pktout.body = pktout.data+4+pad+1; +} + +static void s_wrpkt(void) { + int pad, len, biglen, i; + unsigned long crc; + + len = pktout.length + 5; /* type and CRC */ + pad = 8 - (len%8); + biglen = len + pad; + + pktout.body[-1] = pktout.type; + for (i=0; iencrypt(pktout.data+4, biglen); + + s_write(pktout.data, biglen+4); +} + +static int do_ssh_init(void) { + char c; + char version[10]; + char vstring[40]; + int i; + +#ifdef FWHACK + i = 0; + while (s_read(&c, 1) == 1) { + if (c == 'S' && i < 2) i++; + else if (c == 'S' && i == 2) i = 2; + else if (c == 'H' && i == 2) break; + else i = 0; + } +#else + if (s_read(&c,1) != 1 || c != 'S') return 0; + if (s_read(&c,1) != 1 || c != 'S') return 0; + if (s_read(&c,1) != 1 || c != 'H') return 0; +#endif + if (s_read(&c,1) != 1 || c != '-') return 0; + i = 0; + while (1) { + if (s_read(&c,1) != 1) + return 0; + if (i >= 0) { + if (c == '-') { + version[i] = '\0'; + i = -1; + } else if (i < sizeof(version)-1) + version[i++] = c; + } + else if (c == '\n') + break; + } + + sprintf(vstring, "SSH-%s-7.7.7\n", + (strcmp(version, "1.5") <= 0 ? version : "1.5")); + s_write(vstring, strlen(vstring)); + return 1; +} + + +/* + * Login on the server and request execution of the command. + */ +static void ssh_login(char *username, char *cmd) +{ + int i, j, len; + unsigned char session_id[16]; + unsigned char *rsabuf, *keystr1, *keystr2; + unsigned char cookie[8]; + struct RSAKey servkey, hostkey; + struct MD5Context md5c; + unsigned long supported_ciphers_mask; + int cipher_type; + + extern struct ssh_cipher ssh_3des; + extern struct ssh_cipher ssh_blowfish; + + get_packet(); + + if (pktin.type != SSH_SMSG_PUBLIC_KEY) + fatalbox("Public key packet not received"); + + memcpy(cookie, pktin.body, 8); + + MD5Init(&md5c); + + i = makekey(pktin.body+8, &servkey, &keystr1); + j = makekey(pktin.body+8+i, &hostkey, &keystr2); + + supported_ciphers_mask = GET_32BIT(pktin.body+12+i+j); + + MD5Update(&md5c, keystr2, hostkey.bytes); + MD5Update(&md5c, keystr1, servkey.bytes); + MD5Update(&md5c, pktin.body, 8); + + MD5Final(session_id, &md5c); + + for (i=0; i<32; i++) + session_key[i] = random_byte(); + + len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes); + + rsabuf = malloc(len); + if (!rsabuf) + fatalbox("Out of memory"); + + verify_ssh_host_key(savedhost, &hostkey); + + for (i=0; i<32; i++) { + rsabuf[i] = session_key[i]; + if (i < 16) + rsabuf[i] ^= session_id[i]; + } + + if (hostkey.bytes > servkey.bytes) { + rsaencrypt(rsabuf, 32, &servkey); + rsaencrypt(rsabuf, servkey.bytes, &hostkey); + } else { + rsaencrypt(rsabuf, 32, &hostkey); + rsaencrypt(rsabuf, hostkey.bytes, &servkey); + } + + cipher_type = cfg.cipher == CIPHER_BLOWFISH ? SSH_CIPHER_BLOWFISH : + SSH_CIPHER_3DES; + if ((supported_ciphers_mask & (1 << cipher_type)) == 0) { + fprintf(stderr, "Selected cipher not supported, falling back to 3DES\n"); + cipher_type = SSH_CIPHER_3DES; + } + + s_wrpkt_start(SSH_CMSG_SESSION_KEY, len+15); + pktout.body[0] = cipher_type; + memcpy(pktout.body+1, cookie, 8); + pktout.body[9] = (len*8) >> 8; + pktout.body[10] = (len*8) & 0xFF; + memcpy(pktout.body+11, rsabuf, len); + pktout.body[len+11] = pktout.body[len+12] = 0; /* protocol flags */ + pktout.body[len+13] = pktout.body[len+14] = 0; + s_wrpkt(); + + free(rsabuf); + + cipher = cipher_type == SSH_CIPHER_BLOWFISH ? &ssh_blowfish : + &ssh_3des; + cipher->sesskey(session_key); + + get_packet(); + + if (pktin.type != SSH_SMSG_SUCCESS) + fatalbox("Encryption not successfully enabled"); + + if (verbose) + fprintf(stderr, "Logging in as \"%s\".\n", username); + s_wrpkt_start(SSH_CMSG_USER, 4+strlen(username)); + pktout.body[0] = pktout.body[1] = pktout.body[2] = 0; + pktout.body[3] = strlen(username); + memcpy(pktout.body+4, username, strlen(username)); + s_wrpkt(); + + get_packet(); + + while (pktin.type == SSH_SMSG_FAILURE) { + char password[100]; + char prompt[200]; + sprintf(prompt, "%s@%s's password: ", username, savedhost); + ssh_get_password(prompt, password, 100); + s_wrpkt_start(SSH_CMSG_AUTH_PASSWORD, 4+strlen(password)); + pktout.body[0] = pktout.body[1] = pktout.body[2] = 0; + pktout.body[3] = strlen(password); + memcpy(pktout.body+4, password, strlen(password)); + s_wrpkt(); + memset(password, 0, strlen(password)); + get_packet(); + if (pktin.type == SSH_SMSG_FAILURE) { + fprintf(stderr, "Access denied\n"); + } else if (pktin.type != SSH_SMSG_SUCCESS) { + fatalbox("Strange packet received, type %d", pktin.type); + } + } + + /* Execute command */ + if (verbose) + fprintf(stderr, "Sending command: %s\n", cmd); + i = strlen(cmd); + s_wrpkt_start(SSH_CMSG_EXEC_CMD, 4+i); + PUT_32BIT(pktout.body, i); + memcpy(pktout.body+4, cmd, i); + s_wrpkt(); +} + + +/* + * Receive a block of data over the SSH link. Block until + * all data is available. Return nr of bytes read (0 if lost connection). + */ +int ssh_recv(unsigned char *buf, int len) +{ + static int pending_input_len = 0; + static unsigned char *pending_input_ptr; + int to_read = len; + + if (pending_input_len >= to_read) { + memcpy(buf, pending_input_ptr, to_read); + pending_input_ptr += to_read; + pending_input_len -= to_read; + return len; + } + + if (pending_input_len > 0) { + memcpy(buf, pending_input_ptr, pending_input_len); + buf += pending_input_len; + to_read -= pending_input_len; + pending_input_len = 0; + } + + if (s == INVALID_SOCKET) + return 0; + while (to_read > 0) { + get_packet(); + if (s == INVALID_SOCKET) + return 0; + if (pktin.type == SSH_SMSG_STDOUT_DATA) { + int plen = GET_32BIT(pktin.body); + if (plen <= to_read) { + memcpy(buf, pktin.body + 4, plen); + buf += plen; + to_read -= plen; + } else { + memcpy(buf, pktin.body + 4, to_read); + pending_input_len = plen - to_read; + pending_input_ptr = pktin.body + 4 + to_read; + to_read = 0; + } + } else if (pktin.type == SSH_SMSG_STDERR_DATA) { + int plen = GET_32BIT(pktin.body); + fwrite(pktin.body + 4, plen, 1, stderr); + } else if (pktin.type == SSH_MSG_DISCONNECT) { + } else if (pktin.type == SSH_SMSG_SUCCESS || + pktin.type == SSH_SMSG_FAILURE) { + } else if (pktin.type == SSH_SMSG_EXIT_STATUS) { + if (verbose) + fprintf(stderr, "Remote exit status %d\n", + GET_32BIT(pktin.body)); + s_wrpkt_start(SSH_CMSG_EXIT_CONFIRMATION, 0); + s_wrpkt(); + if (verbose) + fprintf(stderr, "Closing connection\n"); + closesocket(s); + s = INVALID_SOCKET; + } + } + + return len; +} + + +/* + * Send a block of data over the SSH link. + * Block until all data is sent. + */ +void ssh_send(unsigned char *buf, int len) +{ + if (s == INVALID_SOCKET) + return; + s_wrpkt_start(SSH_CMSG_STDIN_DATA, 4 + len); + PUT_32BIT(pktout.body, len); + memcpy(pktout.body + 4, buf, len); + s_wrpkt(); +} + + +/* + * Send an EOF notification to the server. + */ +void ssh_send_eof(void) +{ + if (s == INVALID_SOCKET) + return; + s_wrpkt_start(SSH_CMSG_EOF, 0); + s_wrpkt(); +} + + +/* + * Set up the connection, login on the remote host and + * start execution of a command. + * + * Returns an error message, or NULL on success. + * + * Also places the canonical host name into `realhost'. + */ +char *ssh_init(char *host, int port, char *cmd, char **realhost) { + SOCKADDR_IN addr; + struct hostent *h; + unsigned long a; +#ifdef FWHACK + char *FWhost; + int FWport; +#endif + + savedhost = malloc(1+strlen(host)); + if (!savedhost) + fatalbox("Out of memory"); + strcpy(savedhost, host); + +#ifdef FWHACK + FWhost = host; + FWport = port; + host = FWSTR; + port = 23; +#endif + + /* + * Try to find host. + */ + if ( (a = inet_addr(host)) == (unsigned long) INADDR_NONE) { + if ( (h = gethostbyname(host)) == NULL) + switch (WSAGetLastError()) { + case WSAENETDOWN: return "Network is down"; + case WSAHOST_NOT_FOUND: case WSANO_DATA: + return "Host does not exist"; + case WSATRY_AGAIN: return "Host not found"; + default: return "gethostbyname: unknown error"; + } + memcpy (&a, h->h_addr, sizeof(a)); + *realhost = h->h_name; + } else + *realhost = host; +#ifdef FWHACK + *realhost = FWhost; +#endif + a = ntohl(a); + + if (port < 0) + port = 22; /* default ssh port */ + + /* + * Open socket. + */ + s = socket(AF_INET, SOCK_STREAM, 0); + if (s == INVALID_SOCKET) + switch (WSAGetLastError()) { + case WSAENETDOWN: return "Network is down"; + case WSAEAFNOSUPPORT: return "TCP/IP support not present"; + default: return "socket(): unknown error"; + } + + /* + * Bind to local address. + */ + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(0); + if (bind (s, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) + switch (WSAGetLastError()) { + case WSAENETDOWN: return "Network is down"; + default: return "bind(): unknown error"; + } + + /* + * Connect to remote address. + */ + addr.sin_addr.s_addr = htonl(a); + addr.sin_port = htons((short)port); + if (connect (s, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) + switch (WSAGetLastError()) { + case WSAENETDOWN: return "Network is down"; + case WSAECONNREFUSED: return "Connection refused"; + case WSAENETUNREACH: return "Network is unreachable"; + case WSAEHOSTUNREACH: return "No route to host"; + default: return "connect(): unknown error"; + } + +#ifdef FWHACK + send(s, "connect ", 8, 0); + send(s, FWhost, strlen(FWhost), 0); + { + char buf[20]; + sprintf(buf, " %d\n", FWport); + send (s, buf, strlen(buf), 0); + } +#endif + + random_init(); + + if (!do_ssh_init()) + return "Protocol initialisation error"; + + ssh_login(cfg.username, cmd); + + return NULL; +} + +/* end */ -- 2.11.0