Added Joris van Rantwijk's scp client
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 31 Aug 1999 09:20:48 +0000 (09:20 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 31 Aug 1999 09:20:48 +0000 (09:20 +0000)
git-svn-id: svn://svn.tartarus.org/sgt/putty@205 cda61777-01e9-0310-a592-d414129be87e

scp.c [new file with mode: 0644]
scp.h [new file with mode: 0644]
scp.ico [new file with mode: 0644]
scp.rc [new file with mode: 0644]
scpssh.c [new file with mode: 0644]

diff --git a/scp.c b/scp.c
new file mode 100644 (file)
index 0000000..2892db2
--- /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 <windows.h>
+#include <winsock.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#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 (file)
index 0000000..160e432
--- /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 (file)
index 0000000..203ece8
Binary files /dev/null and b/scp.ico differ
diff --git a/scp.rc b/scp.rc
new file mode 100644 (file)
index 0000000..627379c
--- /dev/null
+++ b/scp.rc
@@ -0,0 +1,2 @@
+200 ICON "scp.ico"
+
diff --git a/scpssh.c b/scpssh.c
new file mode 100644 (file)
index 0000000..b51cbb8
--- /dev/null
+++ b/scpssh.c
@@ -0,0 +1,566 @@
+/*
+ *  scpssh.c  -  SSH implementation for PuTTY Secure Copy
+ *  Joris van Rantwijk, Aug 1999.
+ *  Based on PuTTY ssh.c by Simon Tatham.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winsock.h>
+
+#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; i<pad; i++)
+       pktout.data[i+4] = random_byte();
+    crc = crc32(pktout.data+4, biglen-4);
+    PUT_32BIT(pktout.data+biglen, crc);
+    PUT_32BIT(pktout.data, len);
+
+    if (cipher)
+       cipher->encrypt(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 */