aboutsummaryrefslogtreecommitdiff
path: root/tools/plink
diff options
context:
space:
mode:
Diffstat (limited to 'tools/plink')
-rw-r--r--tools/plink/errsock.c67
-rw-r--r--tools/plink/winshare.c224
2 files changed, 291 insertions, 0 deletions
diff --git a/tools/plink/errsock.c b/tools/plink/errsock.c
new file mode 100644
index 000000000..1b3a88a25
--- /dev/null
+++ b/tools/plink/errsock.c
@@ -0,0 +1,67 @@
+/*
+ * A dummy Socket implementation which just holds an error message.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+
+typedef struct Socket_error_tag *Error_Socket;
+
+struct Socket_error_tag {
+ const struct socket_function_table *fn;
+ /* the above variable absolutely *must* be the first in this structure */
+
+ char *error;
+ Plug plug;
+};
+
+static Plug sk_error_plug(Socket s, Plug p)
+{
+ Error_Socket ps = (Error_Socket) s;
+ Plug ret = ps->plug;
+ if (p)
+ ps->plug = p;
+ return ret;
+}
+
+static void sk_error_close(Socket s)
+{
+ Error_Socket ps = (Error_Socket) s;
+
+ sfree(ps->error);
+ sfree(ps);
+}
+
+static const char *sk_error_socket_error(Socket s)
+{
+ Error_Socket ps = (Error_Socket) s;
+ return ps->error;
+}
+
+Socket new_error_socket(const char *errmsg, Plug plug)
+{
+ static const struct socket_function_table socket_fn_table = {
+ sk_error_plug,
+ sk_error_close,
+ NULL /* write */,
+ NULL /* write_oob */,
+ NULL /* write_eof */,
+ NULL /* flush */,
+ NULL /* set_frozen */,
+ sk_error_socket_error
+ };
+
+ Error_Socket ret;
+
+ ret = snew(struct Socket_error_tag);
+ ret->fn = &socket_fn_table;
+ ret->plug = plug;
+ ret->error = dupstr(errmsg);
+
+ return (Socket) ret;
+}
diff --git a/tools/plink/winshare.c b/tools/plink/winshare.c
new file mode 100644
index 000000000..17bad46d9
--- /dev/null
+++ b/tools/plink/winshare.c
@@ -0,0 +1,224 @@
+/*
+ * Windows implementation of SSH connection-sharing IPC setup.
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#define DEFINE_PLUG_METHOD_MACROS
+#include "tree234.h"
+#include "putty.h"
+#include "network.h"
+#include "proxy.h"
+#include "ssh.h"
+
+#if !defined NO_SECURITY
+
+#include "winsecur.h"
+
+#define CONNSHARE_PIPE_PREFIX "\\\\.\\pipe\\putty-connshare"
+#define CONNSHARE_MUTEX_PREFIX "Local\\putty-connshare-mutex"
+
+static char *obfuscate_name(const char *realname)
+{
+ /*
+ * Windows's named pipes all live in the same namespace, so one
+ * user can see what pipes another user has open. This is an
+ * undesirable privacy leak and in particular permits one user to
+ * know what username@host another user is SSHing to, so we
+ * protect that information by using CryptProtectMemory (which
+ * uses a key built in to each user's account).
+ */
+ char *cryptdata;
+ int cryptlen;
+ SHA256_State sha;
+ unsigned char lenbuf[4];
+ unsigned char digest[32];
+ char retbuf[65];
+ int i;
+
+ cryptlen = strlen(realname) + 1;
+ cryptlen += CRYPTPROTECTMEMORY_BLOCK_SIZE - 1;
+ cryptlen /= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+ cryptlen *= CRYPTPROTECTMEMORY_BLOCK_SIZE;
+
+ cryptdata = snewn(cryptlen, char);
+ memset(cryptdata, 0, cryptlen);
+ strcpy(cryptdata, realname);
+
+ /*
+ * CRYPTPROTECTMEMORY_CROSS_PROCESS causes CryptProtectMemory to
+ * use the same key in all processes with this user id, meaning
+ * that the next PuTTY process calling this function with the same
+ * input will get the same data.
+ *
+ * (Contrast with CryptProtectData, which invents a new session
+ * key every time since its API permits returning more data than
+ * was input, so calling _that_ and hashing the output would not
+ * be stable.)
+ *
+ * We don't worry too much if this doesn't work for some reason.
+ * Omitting this step still has _some_ privacy value (in that
+ * another user can test-hash things to confirm guesses as to
+ * where you might be connecting to, but cannot invert SHA-256 in
+ * the absence of any plausible guess). So we don't abort if we
+ * can't call CryptProtectMemory at all, or if it fails.
+ */
+ if (got_crypt())
+ p_CryptProtectMemory(cryptdata, cryptlen,
+ CRYPTPROTECTMEMORY_CROSS_PROCESS);
+
+ /*
+ * We don't want to give away the length of the hostname either,
+ * so having got it back out of CryptProtectMemory we now hash it.
+ */
+ SHA256_Init(&sha);
+ PUT_32BIT_MSB_FIRST(lenbuf, cryptlen);
+ SHA256_Bytes(&sha, lenbuf, 4);
+ SHA256_Bytes(&sha, cryptdata, cryptlen);
+ SHA256_Final(&sha, digest);
+
+ sfree(cryptdata);
+
+ /*
+ * Finally, make printable.
+ */
+ for (i = 0; i < 32; i++) {
+ sprintf(retbuf + 2*i, "%02x", digest[i]);
+ /* the last of those will also write the trailing NUL */
+ }
+
+ return dupstr(retbuf);
+}
+
+static char *make_name(const char *prefix, const char *name)
+{
+ char *username, *retname;
+
+ username = get_username();
+ retname = dupprintf("%s.%s.%s", prefix, username, name);
+ sfree(username);
+
+ return retname;
+}
+
+Socket new_named_pipe_client(const char *pipename, Plug plug);
+Socket new_named_pipe_listener(const char *pipename, Plug plug);
+
+int platform_ssh_share(const char *pi_name, Conf *conf,
+ Plug downplug, Plug upplug, Socket *sock,
+ char **logtext, char **ds_err, char **us_err,
+ int can_upstream, int can_downstream)
+{
+ char *name, *mutexname, *pipename;
+ HANDLE mutex;
+ Socket retsock;
+ PSECURITY_DESCRIPTOR psd;
+ PACL acl;
+
+ /*
+ * Transform the platform-independent version of the connection
+ * identifier into the obfuscated version we'll use for our
+ * Windows named pipe and mutex. A side effect of doing this is
+ * that it also eliminates any characters illegal in Windows pipe
+ * names.
+ */
+ name = obfuscate_name(pi_name);
+ if (!name) {
+ *logtext = dupprintf("Unable to call CryptProtectMemory: %s",
+ win_strerror(GetLastError()));
+ return SHARE_NONE;
+ }
+
+ /*
+ * Make a mutex name out of the connection identifier, and lock it
+ * while we decide whether to be upstream or downstream.
+ */
+ {
+ SECURITY_ATTRIBUTES sa;
+
+ mutexname = make_name(CONNSHARE_MUTEX_PREFIX, name);
+ if (!make_private_security_descriptor(MUTEX_ALL_ACCESS,
+ &psd, &acl, logtext)) {
+ sfree(mutexname);
+ return SHARE_NONE;
+ }
+
+ memset(&sa, 0, sizeof(sa));
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = psd;
+ sa.bInheritHandle = FALSE;
+
+ mutex = CreateMutex(&sa, FALSE, mutexname);
+
+ if (!mutex) {
+ *logtext = dupprintf("CreateMutex(\"%s\") failed: %s",
+ mutexname, win_strerror(GetLastError()));
+ sfree(mutexname);
+ LocalFree(psd);
+ LocalFree(acl);
+ return SHARE_NONE;
+ }
+
+ sfree(mutexname);
+ LocalFree(psd);
+ LocalFree(acl);
+
+ WaitForSingleObject(mutex, INFINITE);
+ }
+
+ pipename = make_name(CONNSHARE_PIPE_PREFIX, name);
+
+ *logtext = NULL;
+
+ if (can_downstream) {
+ retsock = new_named_pipe_client(pipename, downplug);
+ if (sk_socket_error(retsock) == NULL) {
+ sfree(*logtext);
+ *logtext = pipename;
+ *sock = retsock;
+ sfree(name);
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ return SHARE_DOWNSTREAM;
+ }
+ sfree(*ds_err);
+ *ds_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
+ sk_close(retsock);
+ }
+
+ if (can_upstream) {
+ retsock = new_named_pipe_listener(pipename, upplug);
+ if (sk_socket_error(retsock) == NULL) {
+ sfree(*logtext);
+ *logtext = pipename;
+ *sock = retsock;
+ sfree(name);
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ return SHARE_UPSTREAM;
+ }
+ sfree(*us_err);
+ *us_err = dupprintf("%s: %s", pipename, sk_socket_error(retsock));
+ sk_close(retsock);
+ }
+
+ /* One of the above clauses ought to have happened. */
+ assert(*logtext || *ds_err || *us_err);
+
+ sfree(pipename);
+ sfree(name);
+ ReleaseMutex(mutex);
+ CloseHandle(mutex);
+ return SHARE_NONE;
+}
+
+void platform_ssh_share_cleanup(const char *name)
+{
+}
+
+#else /* !defined NO_SECURITY */
+
+#include "noshare.c"
+
+#endif /* !defined NO_SECURITY */