mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-10 09:58:01 +00:00
bb78583ad2
The basic strategy is described at the top of the new source file sshshare.c. In very brief: an 'upstream' PuTTY opens a Unix-domain socket or Windows named pipe, and listens for connections from other PuTTYs wanting to run sessions on the same server. The protocol spoken down that socket/pipe is essentially the bare ssh-connection protocol, using a trivial binary packet protocol with no encryption, and the upstream has to do some fiddly transformations that I've been referring to as 'channel-number NAT' to avoid resource clashes between the sessions it's managing. This is quite different from OpenSSH's approach of using the Unix- domain socket as a means of passing file descriptors around; the main reason for that is that fd-passing is Unix-specific but this system has to work on Windows too. However, there are additional advantages, such as making it easy for each downstream PuTTY to run its own independent set of port and X11 forwardings (though the method for making the latter work is quite painful). Sharing is off by default, but configuration is intended to be very easy in the normal case - just tick one box in the SSH config panel and everything else happens automatically. [originally from svn r10083]
228 lines
6.4 KiB
C
228 lines
6.4 KiB
C
/*
|
|
* 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.)
|
|
*/
|
|
if (!p_CryptProtectMemory(cryptdata, cryptlen,
|
|
CRYPTPROTECTMEMORY_CROSS_PROCESS)) {
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
PSID networksid;
|
|
|
|
if (!got_crypt()) {
|
|
*logtext = dupprintf("Unable to load crypt32.dll");
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
/*
|
|
* 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, &networksid,
|
|
&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(networksid);
|
|
LocalFree(acl);
|
|
return SHARE_NONE;
|
|
}
|
|
|
|
sfree(mutexname);
|
|
LocalFree(psd);
|
|
LocalFree(networksid);
|
|
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 */
|