mirror of
https://git.tartarus.org/simon/putty.git
synced 2025-01-09 17:38:00 +00:00
Implement connection sharing between instances of PuTTY.
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]
This commit is contained in:
parent
f6f78f8355
commit
bb78583ad2
7
Recipe
7
Recipe
@ -299,9 +299,10 @@ NONSSH = telnet raw rlogin ldisc pinger
|
||||
SSH = ssh sshcrc sshdes sshmd5 sshrsa sshrand sshsha sshblowf
|
||||
+ sshdh sshcrcda sshpubk sshzlib sshdss x11fwd portfwd
|
||||
+ sshaes sshsh256 sshsh512 sshbn wildcard pinger ssharcf
|
||||
+ sshgssc pgssapi
|
||||
WINSSH = SSH winnoise winsecur winpgntc wingss winhsock errsock
|
||||
UXSSH = SSH uxnoise uxagentc uxgss
|
||||
+ sshgssc pgssapi sshshare
|
||||
WINSSH = SSH winnoise winsecur winpgntc wingss winshare winnps winnpc
|
||||
+ winhsock errsock
|
||||
UXSSH = SSH uxnoise uxagentc uxgss uxshare
|
||||
|
||||
# SFTP implementation (pscp, psftp).
|
||||
SFTP = sftp int64 logging
|
||||
|
20
config.c
20
config.c
@ -2099,6 +2099,26 @@ void setup_config_box(struct controlbox *b, int midsession,
|
||||
I(CONF_compression));
|
||||
}
|
||||
|
||||
if (!midsession || protcfginfo != 1) {
|
||||
s = ctrl_getset(b, "Connection/SSH", "sharing", "Sharing an SSH connection between PuTTY tools");
|
||||
|
||||
ctrl_checkbox(s, "Share SSH connections if possible", 's',
|
||||
HELPCTX(ssh_share),
|
||||
conf_checkbox_handler,
|
||||
I(CONF_ssh_connection_sharing));
|
||||
|
||||
ctrl_text(s, "Permitted roles in a shared connection:",
|
||||
HELPCTX(ssh_share));
|
||||
ctrl_checkbox(s, "Upstream (connecting to the real server)", 'u',
|
||||
HELPCTX(ssh_share),
|
||||
conf_checkbox_handler,
|
||||
I(CONF_ssh_connection_sharing_upstream));
|
||||
ctrl_checkbox(s, "Downstream (connecting to the upstream PuTTY)", 'd',
|
||||
HELPCTX(ssh_share),
|
||||
conf_checkbox_handler,
|
||||
I(CONF_ssh_connection_sharing_downstream));
|
||||
}
|
||||
|
||||
if (!midsession) {
|
||||
s = ctrl_getset(b, "Connection/SSH", "protocol", "Protocol options");
|
||||
|
||||
|
@ -2274,6 +2274,64 @@ If you select \q{1 only} or \q{2 only} here, PuTTY will only connect
|
||||
if the server you connect to offers the SSH protocol version you
|
||||
have specified.
|
||||
|
||||
\S{config-ssh-sharing} Sharing an SSH connection between PuTTY tools
|
||||
|
||||
The controls in this box allow you to configure PuTTY to reuse an
|
||||
existing SSH connection, where possible.
|
||||
|
||||
The SSH-2 protocol permits you to run multiple data channels over the
|
||||
same SSH connection, so that you can log in just once (and do the
|
||||
expensive encryption setup just once) and then have more than one
|
||||
terminal window open.
|
||||
|
||||
Each instance of PuTTY can still run at most one terminal session, but
|
||||
using the controls in this box, you can configure PuTTY to check if
|
||||
another instance of itself has already connected to the target host,
|
||||
and if so, share that instance's SSH connection instead of starting a
|
||||
separate new one.
|
||||
|
||||
To enable this feature, just tick the box \q{Share SSH connections if
|
||||
possible}. Then, whenever you start up a PuTTY session connecting to a
|
||||
particular host, it will try to reuse an existing SSH connection if
|
||||
one is available. For example, selecting \q{Duplicate Session} from
|
||||
the system menu will launch another session on the same host, and if
|
||||
sharing is enabled then it will reuse the existing SSH connection.
|
||||
|
||||
When this mode is in use, the first PuTTY that connected to a given
|
||||
server becomes the \q{upstream}, which means that it is the one
|
||||
managing the real SSH connection. All subsequent PuTTYs which reuse
|
||||
the connection are referred to as \q{downstreams}: they do not connect
|
||||
to the real server at all, but instead connect to the upstream PuTTY
|
||||
via local inter-process communication methods.
|
||||
|
||||
For this system to be activated, \e{both} the upstream and downstream
|
||||
instances of PuTTY must have the sharing option enabled.
|
||||
|
||||
The upstream PuTTY can therefore not terminate until all its
|
||||
downstreams have closed. This is similar to the effect you get with
|
||||
port forwarding or X11 forwarding, in which a PuTTY whose terminal
|
||||
session has already finished will still remain open so as to keep
|
||||
serving forwarded connections.
|
||||
|
||||
In case you need to configure this system in more detail, there are
|
||||
two additional checkboxes which allow you to specify whether a
|
||||
particular PuTTY can act as an upstream or a downstream or both.
|
||||
(These boxes only take effect if the main \q{Share SSH connections if
|
||||
possible} box is also ticked.) By default both of these boxes are
|
||||
ticked, so that multiple PuTTYs started from the same configuration
|
||||
will designate one of themselves as the upstream and share a single
|
||||
connection; but if for some reason you need a particular PuTTY
|
||||
configuration \e{not} to be an upstream (e.g. because you definitely
|
||||
need it to close promptly) or not to be a downstream (e.g. because it
|
||||
needs to do its own authentication using a special private key) then
|
||||
you can untick one or the other of these boxes.
|
||||
|
||||
I have referred to \q{PuTTY} throughout the above discussion, but all
|
||||
the other PuTTY tools which make SSH connections can use this
|
||||
mechanism too. For example, if PSCP or PSFTP loads a configuration
|
||||
with sharing enabled, then it can act as a downstream and use an
|
||||
existing SSH connection set up by an instance of GUI PuTTY. The one
|
||||
special case is that PSCP and PSFTP will \e{never} act as upstreams.
|
||||
|
||||
\H{config-ssh-kex} The Kex panel
|
||||
|
||||
|
27
logging.c
27
logging.c
@ -235,7 +235,8 @@ void log_eventlog(void *handle, const char *event)
|
||||
void log_packet(void *handle, int direction, int type,
|
||||
char *texttype, const void *data, int len,
|
||||
int n_blanks, const struct logblank_t *blanks,
|
||||
const unsigned long *seq)
|
||||
const unsigned long *seq,
|
||||
unsigned downstream_id, const char *additional_log_text)
|
||||
{
|
||||
struct LogContext *ctx = (struct LogContext *)handle;
|
||||
char dumpdata[80], smalldata[5];
|
||||
@ -248,15 +249,21 @@ void log_packet(void *handle, int direction, int type,
|
||||
|
||||
/* Packet header. */
|
||||
if (texttype) {
|
||||
if (seq) {
|
||||
logprintf(ctx, "%s packet #0x%lx, type %d / 0x%02x (%s)\r\n",
|
||||
direction == PKT_INCOMING ? "Incoming" : "Outgoing",
|
||||
*seq, type, type, texttype);
|
||||
} else {
|
||||
logprintf(ctx, "%s packet type %d / 0x%02x (%s)\r\n",
|
||||
direction == PKT_INCOMING ? "Incoming" : "Outgoing",
|
||||
type, type, texttype);
|
||||
}
|
||||
logprintf(ctx, "%s packet ",
|
||||
direction == PKT_INCOMING ? "Incoming" : "Outgoing");
|
||||
|
||||
if (seq)
|
||||
logprintf(ctx, "#0x%lx, ", *seq);
|
||||
|
||||
logprintf(ctx, "type %d / 0x%02x (%s)", type, type, texttype);
|
||||
|
||||
if (downstream_id) {
|
||||
logprintf(ctx, " on behalf of downstream #%u", downstream_id);
|
||||
if (additional_log_text)
|
||||
logprintf(ctx, " (%s)", additional_log_text);
|
||||
}
|
||||
|
||||
logprintf(ctx, "\r\n");
|
||||
} else {
|
||||
/*
|
||||
* Raw data is logged with a timestamp, so that it's possible
|
||||
|
@ -100,6 +100,8 @@ Socket new_listener(char *srcaddr, int port, Plug plug, int local_host_only,
|
||||
Conf *conf, int addressfamily);
|
||||
SockAddr name_lookup(char *host, int port, char **canonicalname,
|
||||
Conf *conf, int addressfamily);
|
||||
int proxy_for_destination (SockAddr addr, const char *hostname, int port,
|
||||
Conf *conf);
|
||||
|
||||
/* platform-dependent callback from new_connection() */
|
||||
/* (same caveat about addr as new_connection()) */
|
||||
@ -116,6 +118,7 @@ void sk_cleanup(void); /* called just before program exit */
|
||||
SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family);
|
||||
SockAddr sk_nonamelookup(const char *host);
|
||||
void sk_getaddr(SockAddr addr, char *buf, int buflen);
|
||||
int sk_addr_needs_port(SockAddr addr);
|
||||
int sk_hostname_is_local(const char *name);
|
||||
int sk_address_is_local(SockAddr addr);
|
||||
int sk_address_is_special_local(SockAddr addr);
|
||||
|
24
noshare.c
Normal file
24
noshare.c
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Stub implementation of SSH connection-sharing IPC, for any
|
||||
* platform which can't support it at all.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "tree234.h"
|
||||
#include "putty.h"
|
||||
#include "ssh.h"
|
||||
#include "network.h"
|
||||
|
||||
int platform_ssh_share(const char *name, Conf *conf,
|
||||
Plug downplug, Plug upplug, Socket *sock,
|
||||
char **logtext)
|
||||
{
|
||||
return SHARE_NONE;
|
||||
}
|
||||
|
||||
void platform_ssh_share_cleanup(const char *name)
|
||||
{
|
||||
}
|
4
proxy.c
4
proxy.c
@ -267,8 +267,8 @@ static int plug_proxy_accepting(Plug p,
|
||||
* This function can accept a NULL pointer as `addr', in which case
|
||||
* it will only check the host name.
|
||||
*/
|
||||
static int proxy_for_destination (SockAddr addr, const char *hostname,
|
||||
int port, Conf *conf)
|
||||
int proxy_for_destination (SockAddr addr, const char *hostname,
|
||||
int port, Conf *conf)
|
||||
{
|
||||
int s = 0, e = 0;
|
||||
char hostip[64];
|
||||
|
3
pscp.c
3
pscp.c
@ -2307,6 +2307,9 @@ void cmdline_error(char *p, ...)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const int share_can_be_downstream = TRUE;
|
||||
const int share_can_be_upstream = FALSE;
|
||||
|
||||
/*
|
||||
* Main program. (Called `psftp_main' because it gets called from
|
||||
* *sftp.c; bit silly, I know, but it had to be called _something_.)
|
||||
|
3
psftp.c
3
psftp.c
@ -2885,6 +2885,9 @@ void cmdline_error(char *p, ...)
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const int share_can_be_downstream = TRUE;
|
||||
const int share_can_be_upstream = FALSE;
|
||||
|
||||
/*
|
||||
* Main program. Parse arguments etc.
|
||||
*/
|
||||
|
6
putty.h
6
putty.h
@ -844,6 +844,9 @@ void cleanup_exit(int);
|
||||
* large window in SSH-2. \
|
||||
*/ \
|
||||
X(INT, NONE, ssh_simple) \
|
||||
X(INT, NONE, ssh_connection_sharing) \
|
||||
X(INT, NONE, ssh_connection_sharing_upstream) \
|
||||
X(INT, NONE, ssh_connection_sharing_downstream) \
|
||||
/* Options for pterm. Should split out into platform-dependent part. */ \
|
||||
X(INT, NONE, stamp_utmp) \
|
||||
X(INT, NONE, login_shell) \
|
||||
@ -1016,7 +1019,8 @@ struct logblank_t {
|
||||
void log_packet(void *logctx, int direction, int type,
|
||||
char *texttype, const void *data, int len,
|
||||
int n_blanks, const struct logblank_t *blanks,
|
||||
const unsigned long *sequence);
|
||||
const unsigned long *sequence,
|
||||
unsigned downstream_id, const char *additional_log_text);
|
||||
|
||||
/*
|
||||
* Exports from testback.c
|
||||
|
@ -641,6 +641,9 @@ void save_open_settings(void *sesskey, Conf *conf)
|
||||
write_setting_i(sesskey, "SerialParity", conf_get_int(conf, CONF_serparity));
|
||||
write_setting_i(sesskey, "SerialFlowControl", conf_get_int(conf, CONF_serflow));
|
||||
write_setting_s(sesskey, "WindowClass", conf_get_str(conf, CONF_winclass));
|
||||
write_setting_i(sesskey, "ConnectionSharing", conf_get_int(conf, CONF_ssh_connection_sharing));
|
||||
write_setting_i(sesskey, "ConnectionSharingUpstream", conf_get_int(conf, CONF_ssh_connection_sharing_upstream));
|
||||
write_setting_i(sesskey, "ConnectionSharingDownstream", conf_get_int(conf, CONF_ssh_connection_sharing_downstream));
|
||||
}
|
||||
|
||||
void load_settings(char *section, Conf *conf)
|
||||
@ -983,6 +986,9 @@ void load_open_settings(void *sesskey, Conf *conf)
|
||||
gppi(sesskey, "SerialParity", SER_PAR_NONE, conf, CONF_serparity);
|
||||
gppi(sesskey, "SerialFlowControl", SER_FLOW_XONXOFF, conf, CONF_serflow);
|
||||
gpps(sesskey, "WindowClass", "", conf, CONF_winclass);
|
||||
gppi(sesskey, "ConnectionSharing", 0, conf, CONF_ssh_connection_sharing);
|
||||
gppi(sesskey, "ConnectionSharingUpstream", 1, conf, CONF_ssh_connection_sharing_upstream);
|
||||
gppi(sesskey, "ConnectionSharingDownstream", 1, conf, CONF_ssh_connection_sharing_downstream);
|
||||
}
|
||||
|
||||
void do_defaults(char *session, Conf *conf)
|
||||
|
60
ssh.h
60
ssh.h
@ -8,12 +8,53 @@
|
||||
#include "misc.h"
|
||||
|
||||
struct ssh_channel;
|
||||
typedef struct ssh_tag *Ssh;
|
||||
|
||||
extern int sshfwd_write(struct ssh_channel *c, char *, int);
|
||||
extern void sshfwd_write_eof(struct ssh_channel *c);
|
||||
extern void sshfwd_unclean_close(struct ssh_channel *c, const char *err);
|
||||
extern void sshfwd_unthrottle(struct ssh_channel *c, int bufsize);
|
||||
Conf *sshfwd_get_conf(struct ssh_channel *c);
|
||||
void sshfwd_x11_sharing_handover(struct ssh_channel *c,
|
||||
void *share_cs, void *share_chan,
|
||||
const char *peer_addr, int peer_port,
|
||||
int endian, int protomajor, int protominor,
|
||||
const void *initial_data, int initial_len);
|
||||
void sshfwd_x11_is_local(struct ssh_channel *c);
|
||||
|
||||
extern Socket ssh_connection_sharing_init(const char *host, int port,
|
||||
Conf *conf, Ssh ssh, void **state);
|
||||
void share_got_pkt_from_server(void *ctx, int type,
|
||||
unsigned char *pkt, int pktlen);
|
||||
void share_activate(void *state, const char *server_verstring);
|
||||
void sharestate_free(void *state);
|
||||
int share_ndownstreams(void *state);
|
||||
|
||||
void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
|
||||
const char *ds_err, const char *us_err);
|
||||
unsigned ssh_alloc_sharing_channel(Ssh ssh, void *sharing_ctx);
|
||||
void ssh_delete_sharing_channel(Ssh ssh, unsigned localid);
|
||||
int ssh_alloc_sharing_rportfwd(Ssh ssh, const char *shost, int sport,
|
||||
void *share_ctx);
|
||||
void ssh_sharing_queue_global_request(Ssh ssh, void *share_ctx);
|
||||
struct X11FakeAuth *ssh_sharing_add_x11_display(Ssh ssh, int authtype,
|
||||
void *share_cs,
|
||||
void *share_chan);
|
||||
void ssh_sharing_remove_x11_display(Ssh ssh, struct X11FakeAuth *auth);
|
||||
void ssh_send_packet_from_downstream(Ssh ssh, unsigned id, int type,
|
||||
const void *pkt, int pktlen,
|
||||
const char *additional_log_text);
|
||||
void ssh_sharing_downstream_connected(Ssh ssh, unsigned id);
|
||||
void ssh_sharing_downstream_disconnected(Ssh ssh, unsigned id);
|
||||
void ssh_sharing_logf(Ssh ssh, unsigned id, const char *logfmt, ...);
|
||||
int ssh_agent_forwarding_permitted(Ssh ssh);
|
||||
void share_setup_x11_channel(void *csv, void *chanv,
|
||||
unsigned upstream_id, unsigned server_id,
|
||||
unsigned server_currwin, unsigned server_maxpkt,
|
||||
unsigned client_adjusted_window,
|
||||
const char *peer_addr, int peer_port, int endian,
|
||||
int protomajor, int protominor,
|
||||
const void *initial_data, int initial_len);
|
||||
|
||||
/*
|
||||
* Useful thing.
|
||||
@ -400,6 +441,7 @@ struct X11FakeAuth {
|
||||
* What to do with an X connection matching this auth data.
|
||||
*/
|
||||
struct X11Display *disp;
|
||||
void *share_cs, *share_chan;
|
||||
};
|
||||
void *x11_make_greeting(int endian, int protomajor, int protominor,
|
||||
int auth_proto, const void *auth_data, int auth_len,
|
||||
@ -450,6 +492,8 @@ char *platform_get_x_display(void);
|
||||
*/
|
||||
void x11_get_auth_from_authfile(struct X11Display *display,
|
||||
const char *authfilename);
|
||||
int x11_identify_auth_proto(const char *proto);
|
||||
void *x11_dehexify(const char *hex, int *outlen);
|
||||
|
||||
Bignum copybn(Bignum b);
|
||||
Bignum bn_power_2(int n);
|
||||
@ -590,6 +634,22 @@ int zlib_compress_block(void *, unsigned char *block, int len,
|
||||
int zlib_decompress_block(void *, unsigned char *block, int len,
|
||||
unsigned char **outblock, int *outlen);
|
||||
|
||||
/*
|
||||
* Connection-sharing API provided by platforms. This function must
|
||||
* either:
|
||||
* - return SHARE_NONE and do nothing
|
||||
* - return SHARE_DOWNSTREAM and set *sock to a Socket connected to
|
||||
* downplug
|
||||
* - return SHARE_UPSTREAM and set *sock to a Socket connected to
|
||||
* upplug.
|
||||
*/
|
||||
enum { SHARE_NONE, SHARE_DOWNSTREAM, SHARE_UPSTREAM };
|
||||
int platform_ssh_share(const char *name, Conf *conf,
|
||||
Plug downplug, Plug upplug, Socket *sock,
|
||||
char **logtext, char **ds_err, char **us_err,
|
||||
int can_upstream, int can_downstream);
|
||||
void platform_ssh_share_cleanup(const char *name);
|
||||
|
||||
/*
|
||||
* SSH-1 message type codes.
|
||||
*/
|
||||
|
55
sshdes.c
55
sshdes.c
@ -1031,3 +1031,58 @@ const struct ssh_cipher ssh_des = {
|
||||
des_encrypt_blk, des_decrypt_blk,
|
||||
8, "single-DES CBC"
|
||||
};
|
||||
|
||||
#ifdef TEST_XDM_AUTH
|
||||
|
||||
/*
|
||||
* Small standalone utility which allows encryption and decryption of
|
||||
* single cipher blocks in the XDM-AUTHORIZATION-1 style. Written
|
||||
* during the rework of X authorisation for connection sharing, to
|
||||
* check the corner case when xa1_firstblock matches but the rest of
|
||||
* the authorisation is bogus.
|
||||
*
|
||||
* Just compile this file on its own with the above ifdef symbol
|
||||
* predefined:
|
||||
|
||||
gcc -DTEST_XDM_AUTH -o sshdes sshdes.c
|
||||
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
void *safemalloc(size_t n, size_t size) { return calloc(n, size); }
|
||||
void safefree(void *p) { return free(p); }
|
||||
void smemclr(void *p, size_t size) { memset(p, 0, size); }
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
unsigned char words[2][8];
|
||||
unsigned char out[8];
|
||||
int i, j;
|
||||
|
||||
memset(words, 0, sizeof(words));
|
||||
|
||||
for (i = 0; i < 2; i++) {
|
||||
for (j = 0; j < 8 && argv[i+1][2*j]; j++) {
|
||||
char x[3];
|
||||
unsigned u;
|
||||
x[0] = argv[i+1][2*j];
|
||||
x[1] = argv[i+1][2*j+1];
|
||||
x[2] = 0;
|
||||
sscanf(x, "%02x", &u);
|
||||
words[i][j] = u;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(out, words[0], 8);
|
||||
des_decrypt_xdmauth(words[1], out, 8);
|
||||
printf("decrypt(%s,%s) = ", argv[1], argv[2]);
|
||||
for (i = 0; i < 8; i++) printf("%02x", out[i]);
|
||||
printf("\n");
|
||||
|
||||
memcpy(out, words[0], 8);
|
||||
des_encrypt_xdmauth(words[1], out, 8);
|
||||
printf("encrypt(%s,%s) = ", argv[1], argv[2]);
|
||||
for (i = 0; i < 8; i++) printf("%02x", out[i]);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
2103
sshshare.c
Normal file
2103
sshshare.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -337,6 +337,15 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen)
|
||||
}
|
||||
}
|
||||
|
||||
int sk_addr_needs_port(SockAddr addr)
|
||||
{
|
||||
if (addr->superfamily == UNRESOLVED || addr->superfamily == UNIX) {
|
||||
return FALSE;
|
||||
} else {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
int sk_hostname_is_local(const char *name)
|
||||
{
|
||||
return !strcmp(name, "localhost") ||
|
||||
|
@ -591,6 +591,9 @@ static void version(void)
|
||||
|
||||
void frontend_net_error_pending(void) {}
|
||||
|
||||
const int share_can_be_downstream = TRUE;
|
||||
const int share_can_be_upstream = TRUE;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int sending;
|
||||
|
@ -122,6 +122,9 @@ char *platform_get_x_display(void) {
|
||||
return dupstr(display);
|
||||
}
|
||||
|
||||
const int share_can_be_downstream = TRUE;
|
||||
const int share_can_be_upstream = TRUE;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
extern int pt_main(int argc, char **argv);
|
||||
|
228
unix/uxshare.c
Normal file
228
unix/uxshare.c
Normal file
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Unix implementation of SSH connection-sharing IPC setup.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/file.h>
|
||||
|
||||
#define DEFINE_PLUG_METHOD_MACROS
|
||||
#include "tree234.h"
|
||||
#include "putty.h"
|
||||
#include "network.h"
|
||||
#include "proxy.h"
|
||||
#include "ssh.h"
|
||||
|
||||
#define CONNSHARE_SOCKETDIR_PREFIX "/tmp/putty-connshare"
|
||||
|
||||
/*
|
||||
* Functions provided by uxnet.c to help connection sharing.
|
||||
*/
|
||||
SockAddr unix_sock_addr(const char *path);
|
||||
Socket new_unix_listener(SockAddr listenaddr, Plug plug);
|
||||
|
||||
static char *make_dirname(const char *name, char **parent_out)
|
||||
{
|
||||
char *username, *dirname, *parent;
|
||||
|
||||
username = get_username();
|
||||
parent = dupprintf("%s.%s", CONNSHARE_SOCKETDIR_PREFIX, username);
|
||||
sfree(username);
|
||||
assert(*parent == '/');
|
||||
|
||||
dirname = dupprintf("%s/%s", parent, name);
|
||||
|
||||
if (parent_out)
|
||||
*parent_out = parent;
|
||||
else
|
||||
sfree(parent);
|
||||
|
||||
return dirname;
|
||||
}
|
||||
|
||||
static char *make_dir_and_check_ours(const char *dirname)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
/*
|
||||
* Create the directory. We might have created it before, so
|
||||
* EEXIST is an OK error; but anything else is doom.
|
||||
*/
|
||||
if (mkdir(dirname, 0700) < 0 && errno != EEXIST)
|
||||
return dupprintf("%s: mkdir: %s", dirname, strerror(errno));
|
||||
|
||||
/*
|
||||
* Now check that that directory is _owned by us_ and not writable
|
||||
* by anybody else. This protects us against somebody else
|
||||
* previously having created the directory in a way that's
|
||||
* writable to us, and thus manipulating us into creating the
|
||||
* actual socket in a directory they can see so that they can
|
||||
* connect to it and use our authenticated SSH sessions.
|
||||
*/
|
||||
if (stat(dirname, &st) < 0)
|
||||
return dupprintf("%s: stat: %s", dirname, strerror(errno));
|
||||
if (st.st_uid != getuid())
|
||||
return dupprintf("%s: directory owned by uid %d, not by us",
|
||||
dirname, st.st_uid);
|
||||
if ((st.st_mode & 077) != 0)
|
||||
return dupprintf("%s: directory has overgenerous permissions %03o"
|
||||
" (expected 700)", dirname, st.st_mode & 0777);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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, *parentdirname, *dirname, *lockname, *sockname, *err;
|
||||
int lockfd;
|
||||
Socket retsock;
|
||||
|
||||
/*
|
||||
* Transform the platform-independent version of the connection
|
||||
* identifier into something valid for a Unix socket, by escaping
|
||||
* slashes (and, while we're here, any control characters).
|
||||
*/
|
||||
{
|
||||
const char *p;
|
||||
char *q;
|
||||
|
||||
name = snewn(1+3*strlen(pi_name), char);
|
||||
|
||||
for (p = pi_name, q = name; *p; p++) {
|
||||
if (*p == '/' || *p == '%' ||
|
||||
(unsigned char)*p < 0x20 || *p == 0x7f) {
|
||||
q += sprintf(q, "%%%02x", (unsigned char)*p);
|
||||
} else {
|
||||
*q++ = *p;
|
||||
}
|
||||
}
|
||||
*q = '\0';
|
||||
}
|
||||
|
||||
/*
|
||||
* First, make sure our subdirectory exists. We must create two
|
||||
* levels of directory - the one for this particular connection,
|
||||
* and the containing one for our username.
|
||||
*/
|
||||
dirname = make_dirname(name, &parentdirname);
|
||||
if ((err = make_dir_and_check_ours(parentdirname)) != NULL) {
|
||||
*logtext = err;
|
||||
sfree(dirname);
|
||||
sfree(parentdirname);
|
||||
sfree(name);
|
||||
return SHARE_NONE;
|
||||
}
|
||||
sfree(parentdirname);
|
||||
if ((err = make_dir_and_check_ours(dirname)) != NULL) {
|
||||
*logtext = err;
|
||||
sfree(dirname);
|
||||
sfree(name);
|
||||
return SHARE_NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Acquire a lock on a file in that directory.
|
||||
*/
|
||||
lockname = dupcat(dirname, "/lock", (char *)NULL);
|
||||
lockfd = open(lockname, O_CREAT | O_RDWR | O_TRUNC, 0600);
|
||||
if (lockfd < 0) {
|
||||
*logtext = dupprintf("%s: open: %s", lockname, strerror(errno));
|
||||
sfree(dirname);
|
||||
sfree(lockname);
|
||||
sfree(name);
|
||||
return SHARE_NONE;
|
||||
}
|
||||
if (flock(lockfd, LOCK_EX) < 0) {
|
||||
*logtext = dupprintf("%s: flock(LOCK_EX): %s",
|
||||
lockname, strerror(errno));
|
||||
sfree(dirname);
|
||||
sfree(lockname);
|
||||
close(lockfd);
|
||||
sfree(name);
|
||||
return SHARE_NONE;
|
||||
}
|
||||
|
||||
sockname = dupprintf("%s/socket", dirname);
|
||||
|
||||
*logtext = NULL;
|
||||
|
||||
if (can_downstream) {
|
||||
retsock = new_connection(unix_sock_addr(sockname),
|
||||
"", 0, 0, 1, 0, 0, downplug, conf);
|
||||
if (sk_socket_error(retsock) == NULL) {
|
||||
sfree(*logtext);
|
||||
*logtext = sockname;
|
||||
*sock = retsock;
|
||||
sfree(dirname);
|
||||
sfree(lockname);
|
||||
close(lockfd);
|
||||
sfree(name);
|
||||
return SHARE_DOWNSTREAM;
|
||||
}
|
||||
sfree(*ds_err);
|
||||
*ds_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
|
||||
sk_close(retsock);
|
||||
}
|
||||
|
||||
if (can_upstream) {
|
||||
retsock = new_unix_listener(unix_sock_addr(sockname), upplug);
|
||||
if (sk_socket_error(retsock) == NULL) {
|
||||
sfree(*logtext);
|
||||
*logtext = sockname;
|
||||
*sock = retsock;
|
||||
sfree(dirname);
|
||||
sfree(lockname);
|
||||
close(lockfd);
|
||||
sfree(name);
|
||||
return SHARE_UPSTREAM;
|
||||
}
|
||||
sfree(*us_err);
|
||||
*us_err = dupprintf("%s: %s", sockname, sk_socket_error(retsock));
|
||||
sk_close(retsock);
|
||||
}
|
||||
|
||||
/* One of the above clauses ought to have happened. */
|
||||
assert(*logtext || *ds_err || *us_err);
|
||||
|
||||
sfree(dirname);
|
||||
sfree(lockname);
|
||||
sfree(sockname);
|
||||
close(lockfd);
|
||||
sfree(name);
|
||||
return SHARE_NONE;
|
||||
}
|
||||
|
||||
void platform_ssh_share_cleanup(const char *name)
|
||||
{
|
||||
char *dirname, *filename;
|
||||
|
||||
dirname = make_dirname(name, NULL);
|
||||
|
||||
filename = dupcat(dirname, "/socket", (char *)NULL);
|
||||
remove(filename);
|
||||
sfree(filename);
|
||||
|
||||
filename = dupcat(dirname, "/lock", (char *)NULL);
|
||||
remove(filename);
|
||||
sfree(filename);
|
||||
|
||||
rmdir(dirname);
|
||||
|
||||
/*
|
||||
* We deliberately _don't_ clean up the parent directory
|
||||
* /tmp/putty-connshare.<username>, because if we leave it around
|
||||
* then it reduces the ability for other users to be a nuisance by
|
||||
* putting their own directory in the way of it.
|
||||
*/
|
||||
|
||||
sfree(dirname);
|
||||
}
|
@ -215,6 +215,9 @@ static UINT wm_mousewheel = WM_MOUSEWHEEL;
|
||||
(((wch) >= 0x180B && (wch) <= 0x180D) || /* MONGOLIAN FREE VARIATION SELECTOR */ \
|
||||
((wch) >= 0xFE00 && (wch) <= 0xFE0F)) /* VARIATION SELECTOR 1-16 */
|
||||
|
||||
const int share_can_be_downstream = TRUE;
|
||||
const int share_can_be_upstream = TRUE;
|
||||
|
||||
/* Dummy routine, only required in plink. */
|
||||
void ldisc_update(void *frontend, int echo, int edit)
|
||||
{
|
||||
|
@ -99,6 +99,7 @@
|
||||
#define WINHELP_CTX_ssh_protocol "ssh.protocol:config-ssh-prot"
|
||||
#define WINHELP_CTX_ssh_command "ssh.command:config-command"
|
||||
#define WINHELP_CTX_ssh_compress "ssh.compress:config-ssh-comp"
|
||||
#define WINHELP_CTX_ssh_share "ssh.auth.gssapi:config-ssh-share"
|
||||
#define WINHELP_CTX_ssh_kexlist "ssh.kex.order:config-ssh-kex-order"
|
||||
#define WINHELP_CTX_ssh_kex_repeat "ssh.kex.repeat:config-ssh-kex-rekey"
|
||||
#define WINHELP_CTX_ssh_auth_bypass "ssh.auth.bypass:config-ssh-noauth"
|
||||
|
@ -81,6 +81,8 @@ struct SockAddr_tag {
|
||||
int refcount;
|
||||
char *error;
|
||||
int resolved;
|
||||
int namedpipe; /* indicates that this SockAddr is phony, holding a Windows
|
||||
* named pipe pathname instead of a network address */
|
||||
#ifndef NO_IPV6
|
||||
struct addrinfo *ais; /* Addresses IPv6 style. */
|
||||
#endif
|
||||
@ -504,6 +506,7 @@ SockAddr sk_namelookup(const char *host, char **canonicalname,
|
||||
#ifndef NO_IPV6
|
||||
ret->ais = NULL;
|
||||
#endif
|
||||
ret->namedpipe = FALSE;
|
||||
ret->addresses = NULL;
|
||||
ret->resolved = FALSE;
|
||||
ret->refcount = 1;
|
||||
@ -610,6 +613,7 @@ SockAddr sk_nonamelookup(const char *host)
|
||||
#ifndef NO_IPV6
|
||||
ret->ais = NULL;
|
||||
#endif
|
||||
ret->namedpipe = FALSE;
|
||||
ret->addresses = NULL;
|
||||
ret->naddresses = 0;
|
||||
ret->refcount = 1;
|
||||
@ -618,6 +622,23 @@ SockAddr sk_nonamelookup(const char *host)
|
||||
return ret;
|
||||
}
|
||||
|
||||
SockAddr sk_namedpipe_addr(const char *pipename)
|
||||
{
|
||||
SockAddr ret = snew(struct SockAddr_tag);
|
||||
ret->error = NULL;
|
||||
ret->resolved = FALSE;
|
||||
#ifndef NO_IPV6
|
||||
ret->ais = NULL;
|
||||
#endif
|
||||
ret->namedpipe = TRUE;
|
||||
ret->addresses = NULL;
|
||||
ret->naddresses = 0;
|
||||
ret->refcount = 1;
|
||||
strncpy(ret->hostname, pipename, lenof(ret->hostname));
|
||||
ret->hostname[lenof(ret->hostname)-1] = '\0';
|
||||
return ret;
|
||||
}
|
||||
|
||||
int sk_nextaddr(SockAddr addr, SockAddrStep *step)
|
||||
{
|
||||
#ifndef NO_IPV6
|
||||
@ -671,6 +692,11 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen)
|
||||
}
|
||||
}
|
||||
|
||||
int sk_addr_needs_port(SockAddr addr)
|
||||
{
|
||||
return addr->namedpipe ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
int sk_hostname_is_local(const char *name)
|
||||
{
|
||||
return !strcmp(name, "localhost") ||
|
||||
|
@ -30,15 +30,36 @@ Socket new_named_pipe_client(const char *pipename, Plug plug)
|
||||
assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0);
|
||||
assert(strchr(pipename + 9, '\\') == NULL);
|
||||
|
||||
pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
|
||||
while (1) {
|
||||
pipehandle = CreateFile(pipename, GENERIC_READ | GENERIC_WRITE,
|
||||
0, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED, NULL);
|
||||
|
||||
if (pipehandle == INVALID_HANDLE_VALUE) {
|
||||
err = dupprintf("Unable to open named pipe '%s': %s",
|
||||
pipename, win_strerror(GetLastError()));
|
||||
ret = new_error_socket(err, plug);
|
||||
sfree(err);
|
||||
return ret;
|
||||
if (pipehandle != INVALID_HANDLE_VALUE)
|
||||
break;
|
||||
|
||||
if (GetLastError() != ERROR_PIPE_BUSY) {
|
||||
err = dupprintf("Unable to open named pipe '%s': %s",
|
||||
pipename, win_strerror(GetLastError()));
|
||||
ret = new_error_socket(err, plug);
|
||||
sfree(err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we got ERROR_PIPE_BUSY, wait for the server to
|
||||
* create a new pipe instance. (Since the server is
|
||||
* expected to be winnps.c, which will do that immediately
|
||||
* after a previous connection is accepted, that shouldn't
|
||||
* take excessively long.)
|
||||
*/
|
||||
if (!WaitNamedPipe(pipename, NMPWAIT_USE_DEFAULT_WAIT)) {
|
||||
err = dupprintf("Error waiting for named pipe '%s': %s",
|
||||
pipename, win_strerror(GetLastError()));
|
||||
ret = new_error_socket(err, plug);
|
||||
sfree(err);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if ((usersid = get_user_sid()) == NULL) {
|
||||
@ -49,10 +70,10 @@ Socket new_named_pipe_client(const char *pipename, Plug plug)
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
&pipeowner, NULL, NULL, NULL,
|
||||
&psd) != ERROR_SUCCESS) {
|
||||
if (p_GetSecurityInfo(pipehandle, SE_KERNEL_OBJECT,
|
||||
OWNER_SECURITY_INFORMATION,
|
||||
&pipeowner, NULL, NULL, NULL,
|
||||
&psd) != ERROR_SUCCESS) {
|
||||
err = dupprintf("Unable to get named pipe security information: %s",
|
||||
win_strerror(GetLastError()));
|
||||
ret = new_error_socket(err, plug);
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
#if !defined NO_SECURITY
|
||||
|
||||
#include <aclapi.h>
|
||||
#include "winsecur.h"
|
||||
|
||||
Socket make_handle_socket(HANDLE send_H, HANDLE recv_H, Plug plug,
|
||||
int overlapped);
|
||||
@ -118,6 +118,12 @@ static Socket named_pipe_accept(accept_ctx_t ctx, Plug plug)
|
||||
return make_handle_socket(conn, conn, plug, TRUE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Dummy SockAddr type which just holds a named pipe address. Only
|
||||
* used for calling plug_log from named_pipe_accept_loop() here.
|
||||
*/
|
||||
SockAddr sk_namedpipe_addr(const char *pipename);
|
||||
|
||||
static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps,
|
||||
int got_one_already)
|
||||
{
|
||||
@ -178,7 +184,7 @@ static void named_pipe_accept_loop(Named_Pipe_Server_Socket ps,
|
||||
|
||||
errmsg = dupprintf("Error while listening to named pipe: %s",
|
||||
win_strerror(error));
|
||||
plug_log(ps->plug, 1, NULL /* FIXME: appropriate kind of sockaddr */, 0,
|
||||
plug_log(ps->plug, 1, sk_namedpipe_addr(ps->pipename), 0,
|
||||
errmsg, error);
|
||||
sfree(errmsg);
|
||||
break;
|
||||
@ -209,8 +215,6 @@ Socket new_named_pipe_listener(const char *pipename, Plug plug)
|
||||
};
|
||||
|
||||
Named_Pipe_Server_Socket ret;
|
||||
SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
|
||||
EXPLICIT_ACCESS ea[2];
|
||||
|
||||
ret = snew(struct Socket_named_pipe_server_tag);
|
||||
ret->fn = &socket_fn_table;
|
||||
@ -224,49 +228,9 @@ Socket new_named_pipe_listener(const char *pipename, Plug plug)
|
||||
assert(strncmp(pipename, "\\\\.\\pipe\\", 9) == 0);
|
||||
assert(strchr(pipename + 9, '\\') == NULL);
|
||||
|
||||
if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
|
||||
0, 0, 0, 0, 0, 0, 0, &ret->networksid)) {
|
||||
ret->error = dupprintf("unable to construct SID for rejecting "
|
||||
"remote pipe connections: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
memset(ea, 0, sizeof(ea));
|
||||
ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
|
||||
ea[0].grfAccessMode = GRANT_ACCESS;
|
||||
ea[0].grfInheritance = NO_INHERITANCE;
|
||||
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_NAME;
|
||||
ea[0].Trustee.ptstrName = "CURRENT_USER";
|
||||
ea[1].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
|
||||
ea[1].grfAccessMode = REVOKE_ACCESS;
|
||||
ea[1].grfInheritance = NO_INHERITANCE;
|
||||
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[1].Trustee.ptstrName = (LPTSTR)ret->networksid;
|
||||
|
||||
if (SetEntriesInAcl(2, ea, NULL, &ret->acl) != ERROR_SUCCESS) {
|
||||
ret->error = dupprintf("unable to construct ACL: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret->psd = (PSECURITY_DESCRIPTOR)
|
||||
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
|
||||
if (!ret->psd) {
|
||||
ret->error = dupprintf("unable to allocate security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!InitializeSecurityDescriptor(ret->psd,SECURITY_DESCRIPTOR_REVISION)) {
|
||||
ret->error = dupprintf("unable to initialise security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!SetSecurityDescriptorDacl(ret->psd, TRUE, ret->acl, FALSE)) {
|
||||
ret->error = dupprintf("unable to set DACL in security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
if (!make_private_security_descriptor(GENERIC_READ | GENERIC_WRITE,
|
||||
&ret->psd, &ret->networksid,
|
||||
&ret->acl, &ret->error)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
|
@ -117,12 +117,6 @@ static void unmungestr(char *in, char *out, int outlen)
|
||||
static tree234 *rsakeys, *ssh2keys;
|
||||
|
||||
static int has_security;
|
||||
#ifndef NO_SECURITY
|
||||
DECL_WINDOWS_FUNCTION(extern, DWORD, GetSecurityInfo,
|
||||
(HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
|
||||
PSID *, PSID *, PACL *, PACL *,
|
||||
PSECURITY_DESCRIPTOR *));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Forward references
|
||||
|
@ -288,6 +288,9 @@ void stdouterr_sent(struct handle *h, int new_backlog)
|
||||
}
|
||||
}
|
||||
|
||||
const int share_can_be_downstream = TRUE;
|
||||
const int share_can_be_upstream = TRUE;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int sending;
|
||||
|
@ -26,7 +26,23 @@ int got_advapi(void)
|
||||
GET_WINDOWS_FUNCTION(advapi, OpenProcessToken) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, GetTokenInformation) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, InitializeSecurityDescriptor) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner);
|
||||
GET_WINDOWS_FUNCTION(advapi, SetSecurityDescriptorOwner) &&
|
||||
GET_WINDOWS_FUNCTION(advapi, SetEntriesInAclA);
|
||||
}
|
||||
return successful;
|
||||
}
|
||||
|
||||
int got_crypt(void)
|
||||
{
|
||||
static int attempted = FALSE;
|
||||
static int successful;
|
||||
static HMODULE crypt;
|
||||
|
||||
if (!attempted) {
|
||||
attempted = TRUE;
|
||||
crypt = load_system32_dll("crypt32.dll");
|
||||
successful = crypt &&
|
||||
GET_WINDOWS_FUNCTION(crypt, CryptProtectMemory);
|
||||
}
|
||||
return successful;
|
||||
}
|
||||
@ -83,4 +99,98 @@ PSID get_user_sid(void)
|
||||
return ret;
|
||||
}
|
||||
|
||||
int make_private_security_descriptor(DWORD permissions,
|
||||
PSECURITY_DESCRIPTOR *psd,
|
||||
PSID *networksid,
|
||||
PACL *acl,
|
||||
char **error)
|
||||
{
|
||||
SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY;
|
||||
EXPLICIT_ACCESS ea[3];
|
||||
int ret = FALSE;
|
||||
|
||||
*psd = NULL;
|
||||
*networksid = NULL;
|
||||
*acl = NULL;
|
||||
*error = NULL;
|
||||
|
||||
if (!got_advapi()) {
|
||||
*error = dupprintf("unable to load advapi32.dll");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!AllocateAndInitializeSid(&nt_auth, 1, SECURITY_NETWORK_RID,
|
||||
0, 0, 0, 0, 0, 0, 0, networksid)) {
|
||||
*error = dupprintf("unable to construct SID for "
|
||||
"local same-user access only: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
memset(ea, 0, sizeof(ea));
|
||||
ea[0].grfAccessPermissions = permissions;
|
||||
ea[0].grfAccessMode = REVOKE_ACCESS;
|
||||
ea[0].grfInheritance = NO_INHERITANCE;
|
||||
ea[0].Trustee.TrusteeForm = TRUSTEE_IS_NAME;
|
||||
ea[0].Trustee.ptstrName = "EVERYONE";
|
||||
ea[1].grfAccessPermissions = permissions;
|
||||
ea[1].grfAccessMode = GRANT_ACCESS;
|
||||
ea[1].grfInheritance = NO_INHERITANCE;
|
||||
ea[1].Trustee.TrusteeForm = TRUSTEE_IS_NAME;
|
||||
ea[1].Trustee.ptstrName = "CURRENT_USER";
|
||||
ea[2].grfAccessPermissions = permissions;
|
||||
ea[2].grfAccessMode = REVOKE_ACCESS;
|
||||
ea[2].grfInheritance = NO_INHERITANCE;
|
||||
ea[2].Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
ea[2].Trustee.ptstrName = (LPTSTR)*networksid;
|
||||
|
||||
if (p_SetEntriesInAclA(2, ea, NULL, acl) != ERROR_SUCCESS || *acl == NULL) {
|
||||
*error = dupprintf("unable to construct ACL: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
*psd = (PSECURITY_DESCRIPTOR)
|
||||
LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
|
||||
if (!*psd) {
|
||||
*error = dupprintf("unable to allocate security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!InitializeSecurityDescriptor(*psd, SECURITY_DESCRIPTOR_REVISION)) {
|
||||
*error = dupprintf("unable to initialise security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (!SetSecurityDescriptorDacl(*psd, TRUE, *acl, FALSE)) {
|
||||
*error = dupprintf("unable to set DACL in security descriptor: %s",
|
||||
win_strerror(GetLastError()));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ret = TRUE;
|
||||
|
||||
cleanup:
|
||||
if (!ret) {
|
||||
if (*psd) {
|
||||
LocalFree(*psd);
|
||||
*psd = NULL;
|
||||
}
|
||||
if (*networksid) {
|
||||
LocalFree(*networksid);
|
||||
*networksid = NULL;
|
||||
}
|
||||
if (*acl) {
|
||||
LocalFree(*acl);
|
||||
*acl = NULL;
|
||||
}
|
||||
} else {
|
||||
sfree(*error);
|
||||
*error = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* !defined NO_SECURITY */
|
||||
|
@ -12,6 +12,9 @@
|
||||
#define WINSECUR_GLOBAL extern
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Functions loaded from advapi32.dll.
|
||||
*/
|
||||
DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, OpenProcessToken,
|
||||
(HANDLE, DWORD, PHANDLE));
|
||||
DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, GetTokenInformation,
|
||||
@ -25,8 +28,38 @@ DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, GetSecurityInfo,
|
||||
(HANDLE, SE_OBJECT_TYPE, SECURITY_INFORMATION,
|
||||
PSID *, PSID *, PACL *, PACL *,
|
||||
PSECURITY_DESCRIPTOR *));
|
||||
|
||||
DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, DWORD, SetEntriesInAclA,
|
||||
(ULONG, PEXPLICIT_ACCESS, PACL, PACL *));
|
||||
int got_advapi(void);
|
||||
|
||||
/*
|
||||
* Functions loaded from crypt32.dll.
|
||||
*/
|
||||
DECL_WINDOWS_FUNCTION(WINSECUR_GLOBAL, BOOL, CryptProtectMemory,
|
||||
(LPVOID, DWORD, DWORD));
|
||||
int got_crypt(void);
|
||||
|
||||
/*
|
||||
* Find the SID describing the current user. The return value (if not
|
||||
* NULL for some error-related reason) is smalloced.
|
||||
*/
|
||||
PSID get_user_sid(void);
|
||||
|
||||
/*
|
||||
* Construct a PSECURITY_DESCRIPTOR of the type used for named pipe
|
||||
* servers, i.e. allowing access only to the current user id and also
|
||||
* only local (i.e. not over SMB) connections.
|
||||
*
|
||||
* If this function returns TRUE, then 'psd', 'networksid' and 'acl'
|
||||
* will all have been filled in with memory allocated using LocalAlloc
|
||||
* (and hence must be freed later using LocalFree). If it returns
|
||||
* FALSE, then instead 'error' has been filled with a dynamically
|
||||
* allocated error message.
|
||||
*/
|
||||
int make_private_security_descriptor(DWORD permissions,
|
||||
PSECURITY_DESCRIPTOR *psd,
|
||||
PSID *networksid,
|
||||
PACL *acl,
|
||||
char **error);
|
||||
|
||||
#endif
|
||||
|
227
windows/winshare.c
Normal file
227
windows/winshare.c
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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 */
|
95
x11fwd.c
95
x11fwd.c
@ -75,6 +75,31 @@ struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype)
|
||||
struct X11FakeAuth *auth = snew(struct X11FakeAuth);
|
||||
int i;
|
||||
|
||||
/*
|
||||
* This function has the job of inventing a set of X11 fake auth
|
||||
* data, and adding it to 'authtree'. We must preserve the
|
||||
* property that for any given actual authorisation attempt, _at
|
||||
* most one_ thing in the tree can possibly match it.
|
||||
*
|
||||
* For MIT-MAGIC-COOKIE-1, that's not too difficult: the match
|
||||
* criterion is simply that the entire cookie is correct, so we
|
||||
* just have to make sure we don't make up two cookies the same.
|
||||
* (Vanishingly unlikely, but we check anyway to be sure, and go
|
||||
* round again inventing a new cookie if add234 tells us the one
|
||||
* we thought of is already in use.)
|
||||
*
|
||||
* For XDM-AUTHORIZATION-1, it's a little more fiddly. The setup
|
||||
* with XA1 is that half the cookie is used as a DES key with
|
||||
* which to CBC-encrypt an assortment of stuff. Happily, the stuff
|
||||
* encrypted _begins_ with the other half of the cookie, and the
|
||||
* IV is always zero, which means that any valid XA1 authorisation
|
||||
* attempt for a given cookie must begin with the same cipher
|
||||
* block, consisting of the DES ECB encryption of the first half
|
||||
* of the cookie using the second half as a key. So we compute
|
||||
* that cipher block here and now, and use it as the sorting key
|
||||
* for distinguishing XA1 entries in the tree.
|
||||
*/
|
||||
|
||||
if (authtype == X11_MIT) {
|
||||
auth->proto = X11_MIT;
|
||||
|
||||
@ -118,6 +143,9 @@ struct X11FakeAuth *x11_invent_fake_auth(tree234 *authtree, int authtype)
|
||||
sprintf(auth->datastring + i*2, "%02x",
|
||||
auth->data[i]);
|
||||
|
||||
auth->disp = NULL;
|
||||
auth->share_cs = auth->share_chan = NULL;
|
||||
|
||||
return auth;
|
||||
}
|
||||
|
||||
@ -342,10 +370,20 @@ static char *x11_verify(unsigned long peer_ip, int peer_port,
|
||||
* record that _might_ match.
|
||||
*/
|
||||
if (!strcmp(proto, x11_authnames[X11_MIT])) {
|
||||
/*
|
||||
* Just look up the whole cookie that was presented to us,
|
||||
* which x11_authcmp will compare against the cookies we
|
||||
* currently believe in.
|
||||
*/
|
||||
match_dummy.proto = X11_MIT;
|
||||
match_dummy.datalen = dlen;
|
||||
match_dummy.data = data;
|
||||
} else if (!strcmp(proto, x11_authnames[X11_XDM])) {
|
||||
/*
|
||||
* Look up the first cipher block, against the stored first
|
||||
* cipher blocks for the XDM-AUTHORIZATION-1 cookies we
|
||||
* currently know. (See comment in x11_invent_fake_auth.)
|
||||
*/
|
||||
match_dummy.proto = X11_XDM;
|
||||
match_dummy.xa1_firstblock = data;
|
||||
} else {
|
||||
@ -843,6 +881,7 @@ int x11_send(struct X11Connection *xconn, char *data, int len)
|
||||
|
||||
xconn->auth_protocol[xconn->auth_plen] = '\0'; /* ASCIZ */
|
||||
|
||||
peer_ip = 0; /* placate optimiser */
|
||||
if (x11_parse_ip(xconn->peer_addr, &peer_ip))
|
||||
peer_port = xconn->peer_port;
|
||||
else
|
||||
@ -857,10 +896,25 @@ int x11_send(struct X11Connection *xconn, char *data, int len)
|
||||
}
|
||||
assert(auth_matched);
|
||||
|
||||
/*
|
||||
* If this auth points to a connection-sharing downstream
|
||||
* rather than an X display we know how to connect to
|
||||
* directly, pass it off to the sharing module now.
|
||||
*/
|
||||
if (auth_matched->share_cs) {
|
||||
sshfwd_x11_sharing_handover(xconn->c, auth_matched->share_cs,
|
||||
auth_matched->share_chan,
|
||||
xconn->peer_addr, xconn->peer_port,
|
||||
xconn->firstpkt[0],
|
||||
protomajor, protominor, data, len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Now we know we're going to accept the connection, and what
|
||||
* X display to connect to. Actually connect to it.
|
||||
*/
|
||||
sshfwd_x11_is_local(xconn->c);
|
||||
xconn->disp = auth_matched->disp;
|
||||
xconn->s = new_connection(sk_addr_dup(xconn->disp->addr),
|
||||
xconn->disp->realhost, xconn->disp->port,
|
||||
@ -930,6 +984,44 @@ void x11_send_eof(struct X11Connection *xconn)
|
||||
sshfwd_write_eof(xconn->c);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions used by connection sharing to convert textual
|
||||
* representations of an X11 auth protocol name + hex cookie into our
|
||||
* usual integer protocol id and binary auth data.
|
||||
*/
|
||||
int x11_identify_auth_proto(const char *protoname)
|
||||
{
|
||||
int protocol;
|
||||
|
||||
for (protocol = 1; protocol < lenof(x11_authnames); protocol++)
|
||||
if (!strcmp(protoname, x11_authnames[protocol]))
|
||||
return protocol;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void *x11_dehexify(const char *hex, int *outlen)
|
||||
{
|
||||
int len, i;
|
||||
unsigned char *ret;
|
||||
|
||||
len = strlen(hex) / 2;
|
||||
ret = snewn(len, unsigned char);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
char bytestr[3];
|
||||
unsigned val = 0;
|
||||
bytestr[0] = hex[2*i];
|
||||
bytestr[1] = hex[2*i+1];
|
||||
bytestr[2] = '\0';
|
||||
sscanf(bytestr, "%x", &val);
|
||||
ret[i] = val;
|
||||
}
|
||||
|
||||
*outlen = len;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct an X11 greeting packet, including making up the right
|
||||
* authorisation data.
|
||||
@ -969,7 +1061,8 @@ void *x11_make_greeting(int endian, int protomajor, int protominor,
|
||||
t = time(NULL);
|
||||
PUT_32BIT_MSB_FIRST(realauthdata+14, t);
|
||||
|
||||
des_encrypt_xdmauth(auth_data + 9, realauthdata, authdatalen);
|
||||
des_encrypt_xdmauth((const unsigned char *)auth_data + 9,
|
||||
realauthdata, authdatalen);
|
||||
} else {
|
||||
authdata = realauthdata;
|
||||
authdatalen = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user