From 8def70c3ec6f81f95673c0de67a75b5a6b2e9e1c Mon Sep 17 00:00:00 2001 From: simon Date: Mon, 17 Nov 2008 18:38:09 +0000 Subject: [PATCH] Revamp of the local X11 connection code. We now parse X display strings more rigorously, and then we look up the local X authority data in .Xauthority _ourself_ rather than delegating to an external xauth program. This is (negligibly) more efficient on Unix, assuming I haven't got it wrong in some subtle way, but its major benefit is that we can now support X authority lookups on Windows as well provided the user points us at an appropriate X authority file in the standard format. A new Windows-specific config option has been added for this purpose. git-svn-id: svn://svn.tartarus.org/sgt/putty@8305 cda61777-01e9-0310-a592-d414129be87e --- Recipe | 4 +- doc/config.but | 21 +++ putty.h | 1 + settings.c | 2 + ssh.c | 50 +++--- ssh.h | 88 +++++++++-- unix/ux_x11.c | 151 ++++++------------ unix/uxnet.c | 23 ++- unix/uxsftp.c | 5 +- windows/wincfg.c | 10 ++ windows/winhelp.h | 1 + windows/winmisc.c | 8 - windows/winsftp.c | 6 + windows/winx11.c | 18 +++ x11fwd.c | 451 +++++++++++++++++++++++++++++++++++++----------------- 15 files changed, 530 insertions(+), 309 deletions(-) create mode 100644 windows/winx11.c diff --git a/Recipe b/Recipe index f957288a..dfada9f2 100644 --- a/Recipe +++ b/Recipe @@ -302,10 +302,10 @@ U_BE_NOSSH = be_nos_s uxser nocproxy # keywords [G] for Windows GUI app, [C] for Console app, [X] for # X/GTK Unix app, [U] for command-line Unix app, [M] for Macintosh app. -putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC putty.res LIBS +putty : [G] GUITERM NONSSH WINSSH W_BE_ALL WINMISC winx11 putty.res LIBS puttytel : [G] GUITERM NONSSH W_BE_NOSSH WINMISC puttytel.res LIBS plink : [C] winplink wincons NONSSH WINSSH W_BE_ALL logging WINMISC - + plink.res LIBS + + winx11 plink.res LIBS pscp : [C] pscp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC + pscp.res LIBS psftp : [C] psftp winsftp wincons WINSSH BE_SSH SFTP wildcard WINMISC diff --git a/doc/config.but b/doc/config.but index 750f188c..96f5dbe4 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2728,6 +2728,27 @@ connections fail. PuTTY's default is \cw{MIT-MAGIC-COOKIE-1}. If you change it, you should be sure you know what you're doing. +\S{config-ssh-xauthority} X authority file for local display + +\cfg{winhelp-topic}{ssh.tunnels.xauthority} + +If you are using X11 forwarding, the local X server to which your +forwarded connections are eventually directed may itself require +authorisation. + +Some Windows X servers do not require this: they do authorisation by +simpler means, such as accepting any connection from the local +machine but not from anywhere else. However, if your X server does +require authorisation, then PuTTY needs to know what authorisation +is required. + +One way in which this data might be made available is for the X +server to store it somewhere in a file which has the same format +as the Unix \c{.Xauthority} file. If this is how your Windows X +server works, then you can tell PuTTY where to find this file by +configuring this option. By default, PuTTY will not attempt to find +any authorisation for your local display. + \H{config-ssh-portfwd} \I{port forwarding}The Tunnels panel \cfg{winhelp-topic}{ssh.tunnels.portfwd} diff --git a/putty.h b/putty.h index 070ba313..20cff543 100644 --- a/putty.h +++ b/putty.h @@ -572,6 +572,7 @@ struct config_tag { int x11_forward; char x11_display[128]; int x11_auth; + Filename xauthfile; /* port forwarding */ int lport_acceptall; /* accept conns from hosts other than localhost */ int rport_acceptall; /* same for remote forwarded ports (SSH-2 only) */ diff --git a/settings.c b/settings.c index cbd50d45..26926769 100644 --- a/settings.c +++ b/settings.c @@ -446,6 +446,7 @@ void save_open_settings(void *sesskey, Config *cfg) write_setting_i(sesskey, "X11Forward", cfg->x11_forward); write_setting_s(sesskey, "X11Display", cfg->x11_display); write_setting_i(sesskey, "X11AuthType", cfg->x11_auth); + write_setting_filename(sesskey, "X11AuthFile", cfg->xauthfile); write_setting_i(sesskey, "LocalPortAcceptAll", cfg->lport_acceptall); write_setting_i(sesskey, "RemotePortAcceptAll", cfg->rport_acceptall); wmap(sesskey, "PortForwardings", cfg->portfwd, lenof(cfg->portfwd)); @@ -775,6 +776,7 @@ void load_open_settings(void *sesskey, Config *cfg) gpps(sesskey, "X11Display", "", cfg->x11_display, sizeof(cfg->x11_display)); gppi(sesskey, "X11AuthType", X11_MIT, &cfg->x11_auth); + gppfile(sesskey, "X11AuthFile", &cfg->xauthfile); gppi(sesskey, "LocalPortAcceptAll", 0, &cfg->lport_acceptall); gppi(sesskey, "RemotePortAcceptAll", 0, &cfg->rport_acceptall); diff --git a/ssh.c b/ssh.c index be086594..d30626dc 100644 --- a/ssh.c +++ b/ssh.c @@ -824,7 +824,7 @@ struct ssh_tag { Pkt_KCtx pkt_kctx; Pkt_ACtx pkt_actx; - void *x11auth; + struct X11Display *x11disp; int version; int conn_throttle_count; @@ -4578,8 +4578,8 @@ static void ssh1_smsg_x11_open(Ssh ssh, struct Packet *pktin) c = snew(struct ssh_channel); c->ssh = ssh; - if (x11_init(&c->u.x11.s, ssh->cfg.x11_display, c, - ssh->x11auth, NULL, -1, &ssh->cfg) != NULL) { + if (x11_init(&c->u.x11.s, ssh->x11disp, c, + NULL, -1, &ssh->cfg) != NULL) { logevent("Opening X11 forward connection failed"); sfree(c); send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE, @@ -4914,11 +4914,9 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, } if (ssh->cfg.x11_forward) { - char proto[20], data[64]; logevent("Requesting X11 forwarding"); - ssh->x11auth = x11_invent_auth(proto, sizeof(proto), - data, sizeof(data), ssh->cfg.x11_auth); - x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display); + ssh->x11disp = x11_setup_display(ssh->cfg.x11_display, + ssh->cfg.x11_auth, &ssh->cfg); /* * Note that while we blank the X authentication data here, we don't * take any special action to blank the start of an X11 channel, @@ -4928,14 +4926,19 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen, */ if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) { send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, - PKT_STR, proto, - PKTT_PASSWORD, PKT_STR, data, PKTT_OTHER, - PKT_INT, x11_get_screen_number(ssh->cfg.x11_display), + PKT_STR, ssh->x11disp->remoteauthprotoname, + PKTT_PASSWORD, + PKT_STR, ssh->x11disp->remoteauthdatastring, + PKTT_OTHER, + PKT_INT, ssh->x11disp->screennum, PKT_END); } else { send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING, - PKT_STR, proto, - PKTT_PASSWORD, PKT_STR, data, PKTT_OTHER, PKT_END); + PKT_STR, ssh->x11disp->remoteauthprotoname, + PKTT_PASSWORD, + PKT_STR, ssh->x11disp->remoteauthdatastring, + PKTT_OTHER, + PKT_END); } do { crReturnV; @@ -6939,9 +6942,8 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) if (!ssh->X11_fwd_enabled) error = "X11 forwarding is not enabled"; - else if (x11_init(&c->u.x11.s, ssh->cfg.x11_display, c, - ssh->x11auth, addrstr, peerport, - &ssh->cfg) != NULL) { + else if (x11_init(&c->u.x11.s, ssh->x11disp, c, + addrstr, peerport, &ssh->cfg) != NULL) { error = "Unable to open an X11 connection"; } else { logevent("Opening X11 forward connection succeeded"); @@ -8479,17 +8481,15 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * Potentially enable X11 forwarding. */ if (ssh->mainchan && !ssh->ncmode && ssh->cfg.x11_forward) { - char proto[20], data[64]; logevent("Requesting X11 forwarding"); - ssh->x11auth = x11_invent_auth(proto, sizeof(proto), - data, sizeof(data), ssh->cfg.x11_auth); - x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display); + ssh->x11disp = x11_setup_display(ssh->cfg.x11_display, + ssh->cfg.x11_auth, &ssh->cfg); s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST); ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); ssh2_pkt_addstring(s->pktout, "x11-req"); ssh2_pkt_addbool(s->pktout, 1); /* want reply */ ssh2_pkt_addbool(s->pktout, 0); /* many connections */ - ssh2_pkt_addstring(s->pktout, proto); + ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthprotoname); /* * Note that while we blank the X authentication data here, we don't * take any special action to blank the start of an X11 channel, @@ -8498,9 +8498,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * cookie into the log. */ dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, data); + ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthdatastring); end_log_omission(ssh, s->pktout); - ssh2_pkt_adduint32(s->pktout, x11_get_screen_number(ssh->cfg.x11_display)); + ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum); ssh2_pkt_send(ssh, s->pktout); crWaitUntilV(pktin); @@ -8991,7 +8991,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->fallback_cmd = 0; ssh->pkt_kctx = SSH2_PKTCTX_NOKEX; ssh->pkt_actx = SSH2_PKTCTX_NOAUTH; - ssh->x11auth = NULL; + ssh->x11disp = NULL; ssh->v1_compressing = FALSE; ssh->v2_outgoing_sequence = 0; ssh->ssh1_rdpkt_crstate = 0; @@ -9129,8 +9129,8 @@ static void ssh_free(void *handle) ssh->rportfwds = NULL; } sfree(ssh->deferred_send_data); - if (ssh->x11auth) - x11_free_auth(ssh->x11auth); + if (ssh->x11disp) + x11_free_display(ssh->x11disp); sfree(ssh->do_ssh_init_state); sfree(ssh->do_ssh1_login_state); sfree(ssh->do_ssh2_transport_state); diff --git a/ssh.h b/ssh.h index 1bc7a98f..7e928ff1 100644 --- a/ssh.h +++ b/ssh.h @@ -2,6 +2,7 @@ #include #include "puttymem.h" +#include "tree234.h" #include "network.h" #include "int64.h" #include "misc.h" @@ -327,28 +328,85 @@ extern void pfd_unthrottle(Socket s); extern void pfd_override_throttle(Socket s, int enable); /* Exports from x11fwd.c */ -extern const char *x11_init(Socket *, char *, void *, void *, const char *, - int, const Config *); +enum { + X11_TRANS_IPV4 = 0, X11_TRANS_IPV6 = 6, X11_TRANS_UNIX = 256 +}; +struct X11Display { + /* Broken-down components of the display name itself */ + int unixdomain; + char *hostname; + int displaynum; + int screennum; + /* OSX sometimes replaces all the above with a full Unix-socket pathname */ + char *unixsocketpath; + + /* PuTTY networking SockAddr to connect to the display, and associated + * gubbins */ + SockAddr addr; + int port; + char *realhost; + + /* Auth details we invented for the virtual display on the SSH server. */ + int remoteauthproto; + unsigned char *remoteauthdata; + int remoteauthdatalen; + char *remoteauthprotoname; + char *remoteauthdatastring; + + /* Our local auth details for talking to the real X display. */ + int localauthproto; + unsigned char *localauthdata; + int localauthdatalen; + + /* + * Used inside x11fwd.c to remember recently seen + * XDM-AUTHORIZATION-1 strings, to avoid replay attacks. + */ + tree234 *xdmseen; +}; +/* + * x11_setup_display() parses the display variable and fills in an + * X11Display structure. Some remote auth details are invented; + * the supplied authtype parameter configures the preferred + * authorisation protocol to use at the remote end. The local auth + * details are looked up by calling platform_get_x11_auth. + */ +extern struct X11Display *x11_setup_display(char *display, int authtype, + const Config *); +void x11_free_display(struct X11Display *disp); +extern const char *x11_init(Socket *, struct X11Display *, void *, + const char *, int, const Config *); extern void x11_close(Socket); extern int x11_send(Socket, char *, int); -extern void *x11_invent_auth(char *, int, char *, int, int); -extern void x11_free_auth(void *); extern void x11_unthrottle(Socket s); extern void x11_override_throttle(Socket s, int enable); -extern int x11_get_screen_number(char *display); -void x11_get_real_auth(void *authv, char *display); char *x11_display(const char *display); - /* Platform-dependent X11 functions */ -extern void platform_get_x11_auth(char *display, int *proto, - unsigned char *data, int *datalen); -extern const char platform_x11_best_transport[]; -/* best X11 hostname for this platform if none specified */ -SockAddr platform_get_x11_unix_address(const char *display, int displaynum, - char **canonicalname); -/* make up a SockAddr naming the address for displaynum */ +extern void platform_get_x11_auth(struct X11Display *display, + const Config *); + /* examine a mostly-filled-in X11Display and fill in localauth* */ +extern const int platform_uses_x11_unix_by_default; + /* choose default X transport in the absence of a specified one */ +SockAddr platform_get_x11_unix_address(const char *path, int displaynum); + /* make up a SockAddr naming the address for displaynum */ char *platform_get_x_display(void); -/* allocated local X display string, if any */ + /* allocated local X display string, if any */ +/* Callbacks in x11.c usable _by_ platform X11 functions */ +/* + * This function does the job of platform_get_x11_auth, provided + * it is told where to find a normally formatted .Xauthority file: + * it opens that file, parses it to find an auth record which + * matches the display details in "display", and fills in the + * localauth fields. + * + * It is expected that most implementations of + * platform_get_x11_auth() will work by finding their system's + * .Xauthority file, adjusting the display details if necessary + * for local oddities like Unix-domain socket transport, and + * calling this function to do the rest of the work. + */ +void x11_get_auth_from_authfile(struct X11Display *display, + const char *authfilename); Bignum copybn(Bignum b); Bignum bn_power_2(int n); diff --git a/unix/ux_x11.c b/unix/ux_x11.c index e8d71959..0998069e 100644 --- a/unix/ux_x11.c +++ b/unix/ux_x11.c @@ -5,117 +5,66 @@ #include #include #include +#include + #include "putty.h" #include "ssh.h" +#include "network.h" -void platform_get_x11_auth(char *display, int *protocol, - unsigned char *data, int *datalen) +void platform_get_x11_auth(struct X11Display *disp, const Config *cfg) { - FILE *fp; - char *command; - int maxsize = *datalen; - char *localbuf; - int proto = -1; + char *xauthfile; + int needs_free; - display = x11_display(display); /* - * Normally we should run `xauth list DISPLAYNAME'. However, - * there's an oddity when the display is local: the display - * `localhost:0' (or `:0') should become just `:0'. + * Upgrade an IP-style localhost display to a Unix-socket + * display. */ - if (!strncmp(display, "localhost:", 10) - || !strncmp(display, "unix:", 5)) - command = dupprintf("xauth list %s 2>/dev/null", - strchr(display, ':')); - else - command = dupprintf("xauth list %s 2>/dev/null", display); - sfree(display); - fp = popen(command, "r"); - sfree(command); - - if (!fp) - return; /* assume no auth */ - - localbuf = snewn(maxsize, char); - - while (1) { - /* - * Read a line from stdin, and attempt to parse it into a - * display name (ignored), auth protocol, and auth string. - */ - int c, i, hexdigit; - char protoname[64]; - - /* Skip the display name. */ - while (c = getc(fp), c != EOF && c != '\n' && !isspace(c)); - if (c == EOF) break; - if (c == '\n') continue; - - /* Skip white space. */ - while (c != EOF && c != '\n' && isspace(c)) - c = getc(fp); - if (c == EOF) break; - if (c == '\n') continue; - - /* Read the auth protocol name, and see if it matches any we - * know about. */ - i = 0; - while (c != EOF && c != '\n' && !isspace(c)) { - if (i < lenof(protoname)-1) protoname[i++] = c; - c = getc(fp); - } - protoname[i] = '\0'; - - for (i = X11_NO_AUTH; ++i < X11_NAUTHS ;) { - if (!strcmp(protoname, x11_authnames[i])) - break; - } - if (i >= X11_NAUTHS || i <= proto) { - /* Unrecognised protocol name, or a worse one than we already have. - * Skip this line. */ - while (c != EOF && c != '\n') - c = getc(fp); - if (c == EOF) break; - } - proto = i; + if (!disp->unixdomain && sk_address_is_local(disp->addr)) { + sk_addr_free(disp->addr); + disp->unixdomain = TRUE; + disp->addr = platform_get_x11_unix_address(NULL, disp->displaynum); + disp->realhost = dupprintf("unix:%d", disp->displaynum); + disp->port = 0; + } - /* Skip white space. */ - while (c != EOF && c != '\n' && isspace(c)) - c = getc(fp); - if (c == EOF) break; - if (c == '\n') continue; + /* + * Set the hostname for Unix-socket displays, so that we'll + * look it up correctly in the X authority file. + */ + if (disp->unixdomain) { + int len; - /* - * Now grab pairs of hex digits and shove them into `data'. - */ - i = 0; - hexdigit = -1; - while (c != EOF && c != '\n') { - int hexval = -1; - if (c >= 'A' && c <= 'F') - hexval = c + 10 - 'A'; - if (c >= 'a' && c <= 'f') - hexval = c + 10 - 'a'; - if (c >= '0' && c <= '9') - hexval = c - '0'; - if (hexval >= 0) { - if (hexdigit >= 0) { - hexdigit = (hexdigit << 4) + hexval; - if (i < maxsize) - localbuf[i++] = hexdigit; - hexdigit = -1; - } else - hexdigit = hexval; - } - c = getc(fp); - } + sfree(disp->hostname); + len = 128; + do { + len *= 2; + disp->hostname = snewn(len, char); + if (gethostname(disp->hostname, len) < 0) { + disp->hostname = NULL; + return; + } + } while (strlen(disp->hostname) >= len-1); + } - *datalen = i; - *protocol = proto; - memcpy(data, localbuf, i); + /* + * Find the .Xauthority file. + */ + needs_free = FALSE; + xauthfile = getenv("XAUTHORITY"); + if (!xauthfile) { + xauthfile = getenv("HOME"); + if (xauthfile) { + xauthfile = dupcat(xauthfile, "/.Xauthority", NULL); + needs_free = TRUE; + } + } - /* Nonetheless, continue looping round; we might find a better one. */ + if (xauthfile) { + x11_get_auth_from_authfile(disp, xauthfile); + if (needs_free) + sfree(xauthfile); } - pclose(fp); - sfree(localbuf); } + +const int platform_uses_x11_unix_by_default = TRUE; diff --git a/unix/uxnet.c b/unix/uxnet.c index 46c87498..c0f24719 100644 --- a/unix/uxnet.c +++ b/unix/uxnet.c @@ -1390,8 +1390,7 @@ int net_service_lookup(char *service) return 0; } -SockAddr platform_get_x11_unix_address(const char *display, int displaynum, - char **canonicalname) +SockAddr platform_get_x11_unix_address(const char *sockpath, int displaynum) { SockAddr ret = snew(struct SockAddr_tag); int n; @@ -1399,26 +1398,22 @@ SockAddr platform_get_x11_unix_address(const char *display, int displaynum, memset(ret, 0, sizeof *ret); ret->superfamily = UNIX; /* - * Mac OS X Leopard uses an innovative X display naming - * convention in which the entire display name is the path to - * the Unix socket, including the trailing :0 which only - * _looks_ like a display number. Heuristically, I think - * detecting this by means of a leading slash ought to be - * adequate. + * In special circumstances (notably Mac OS X Leopard), we'll + * have been passed an explicit Unix socket path. */ - if (display[0] == '/') { + if (sockpath) { n = snprintf(ret->hostname, sizeof ret->hostname, - "%s", display); + "%s", sockpath); } else { n = snprintf(ret->hostname, sizeof ret->hostname, "%s%d", X11_UNIX_PATH, displaynum); } - if(n < 0) + + if (n < 0) ret->error = "snprintf failed"; - else if(n >= sizeof ret->hostname) + else if (n >= sizeof ret->hostname) ret->error = "X11 UNIX name too long"; - else - *canonicalname = dupstr(ret->hostname); + #ifndef NO_IPV6 ret->ais = NULL; #else diff --git a/unix/uxsftp.c b/unix/uxsftp.c index fd6b528b..a9af614f 100644 --- a/unix/uxsftp.c +++ b/unix/uxsftp.c @@ -18,6 +18,7 @@ #endif #include "putty.h" +#include "ssh.h" #include "psftp.h" #include "int64.h" @@ -33,11 +34,11 @@ char *x_get_default(const char *key) return NULL; /* this is a stub */ } -void platform_get_x11_auth(char *display, int *protocol, - unsigned char *data, int *datalen) +void platform_get_x11_auth(struct X11Display *display, const Config *cfg) { /* Do nothing, therefore no auth. */ } +const int platform_uses_x11_unix_by_default = TRUE; /* * Default settings that are specific to PSFTP. diff --git a/windows/wincfg.c b/windows/wincfg.c index bc6102d8..516f0a3d 100644 --- a/windows/wincfg.c +++ b/windows/wincfg.c @@ -377,4 +377,14 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help, */ if (!midsession || (protocol == PROT_SERIAL)) ser_setup_config_box(b, midsession, 0x1F, 0x0F); + + /* + * $XAUTHORITY is not reliable on Windows, so we provide a + * means to override it. + */ + s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding"); + ctrl_filesel(s, "X authority file for local display", 't', + NULL, FALSE, "Select X authority file", + HELPCTX(ssh_tunnels_xauthority), + dlg_stdfilesel_handler, I(offsetof(Config, xauthfile))); } diff --git a/windows/winhelp.h b/windows/winhelp.h index a91aaf12..fc0b8370 100644 --- a/windows/winhelp.h +++ b/windows/winhelp.h @@ -121,6 +121,7 @@ #define WINHELP_CTX_translation_linedraw "translation.linedraw:config-linedraw" #define WINHELP_CTX_ssh_tunnels_x11 "ssh.tunnels.x11:config-ssh-x11" #define WINHELP_CTX_ssh_tunnels_x11auth "ssh.tunnels.x11auth:config-ssh-x11auth" +#define WINHELP_CTX_ssh_tunnels_xauthority "ssh.tunnels.xauthority:config-ssh-xauthority" #define WINHELP_CTX_ssh_tunnels_portfwd "ssh.tunnels.portfwd:config-ssh-portfwd" #define WINHELP_CTX_ssh_tunnels_portfwd_localhost "ssh.tunnels.portfwd.localhost:config-ssh-portfwd-localhost" #define WINHELP_CTX_ssh_tunnels_portfwd_ipversion "ssh.tunnels.portfwd.ipversion:config-ssh-portfwd-address-family" diff --git a/windows/winmisc.c b/windows/winmisc.c index b9214043..e5143d92 100644 --- a/windows/winmisc.c +++ b/windows/winmisc.c @@ -8,14 +8,6 @@ OSVERSIONINFO osVersion; -void platform_get_x11_auth(char *display, int *proto, - unsigned char *data, int *datalen) -{ - /* We don't support this at all under Windows. */ -} - -const char platform_x11_best_transport[] = "localhost"; - char *platform_get_x_display(void) { /* We may as well check for DISPLAY in case it's useful. */ return dupstr(getenv("DISPLAY")); diff --git a/windows/winsftp.c b/windows/winsftp.c index f3c0a6fb..94d04a75 100644 --- a/windows/winsftp.c +++ b/windows/winsftp.c @@ -19,6 +19,12 @@ int get_userpass_input(prompts_t *p, unsigned char *in, int inlen) return ret; } +void platform_get_x11_auth(struct X11Display *display, const Config *cfg) +{ + /* Do nothing, therefore no auth. */ +} +const int platform_uses_x11_unix_by_default = TRUE; + /* ---------------------------------------------------------------------- * File access abstraction. */ diff --git a/windows/winx11.c b/windows/winx11.c new file mode 100644 index 00000000..c8951b08 --- /dev/null +++ b/windows/winx11.c @@ -0,0 +1,18 @@ +/* + * winx11.c: fetch local auth data for X forwarding. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +void platform_get_x11_auth(struct X11Display *disp, const Config *cfg) +{ + if (cfg->xauthfile.path[0]) + x11_get_auth_from_authfile(disp, cfg->xauthfile.path); +} + +const int platform_uses_x11_unix_by_default = FALSE; diff --git a/x11fwd.c b/x11fwd.c index 4621ac9b..50300ad1 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -26,18 +26,11 @@ struct XDMSeen { unsigned char clientid[6]; }; -struct X11Auth { - unsigned char fakedata[64], realdata[64]; - int fakeproto, realproto; - int fakelen, reallen; - tree234 *xdmseen; -}; - struct X11Private { const struct plug_function_table *fn; /* the above variable absolutely *must* be the first in this structure */ unsigned char firstpkt[12]; /* first X data packet */ - struct X11Auth *auth; + struct X11Display *disp; char *auth_protocol; unsigned char *auth_data; int data_read, auth_plen, auth_psize, auth_dlen, auth_dsize; @@ -57,86 +50,197 @@ static int xdmseen_cmp(void *a, void *b) memcmp(sa->clientid, sb->clientid, sizeof(sa->clientid)); } -void *x11_invent_auth(char *proto, int protomaxlen, - char *data, int datamaxlen, int proto_id) +struct X11Display *x11_setup_display(char *display, int authtype, + const Config *cfg) { - struct X11Auth *auth = snew(struct X11Auth); - char ourdata[64]; + struct X11Display *disp = snew(struct X11Display); + char *localcopy; int i; - if (proto_id == X11_MIT) { - auth->fakeproto = X11_MIT; + if (!display || !*display) { + localcopy = platform_get_x_display(); + if (!localcopy || !*localcopy) { + sfree(localcopy); + localcopy = dupstr(":0"); /* plausible default for any platform */ + } + } else + localcopy = dupstr(display); + + /* + * Parse the display name. + * + * We expect this to have one of the following forms: + * + * - the standard X format which looks like + * [ [ protocol '/' ] host ] ':' displaynumber [ '.' screennumber ] + * (X11 also permits a double colon to indicate DECnet, but + * that's not our problem, thankfully!) + * + * - only seen in the wild on MacOS (so far): a pathname to a + * Unix-domain socket, which will typically and confusingly + * end in ":0", and which I'm currently distinguishing from + * the standard scheme by noting that it starts with '/'. + */ + if (localcopy[0] == '/') { + disp->unixsocketpath = localcopy; + disp->unixdomain = TRUE; + disp->hostname = NULL; + disp->displaynum = -1; + disp->screennum = 0; + } else { + char *colon, *dot, *slash; + char *protocol, *hostname; + + colon = strrchr(localcopy, ':'); + if (!colon) { + sfree(disp); + sfree(localcopy); + return NULL; /* FIXME: report a specific error? */ + } + + *colon++ = '\0'; + dot = strchr(colon, '.'); + if (dot) + *dot++ = '\0'; + + disp->displaynum = atoi(colon); + if (dot) + disp->screennum = atoi(dot); + else + disp->screennum = 0; + + protocol = NULL; + hostname = localcopy; + if (colon > localcopy) { + slash = strchr(localcopy, '/'); + if (slash) { + *slash++ = '\0'; + protocol = localcopy; + hostname = slash; + } + } + + disp->hostname = *hostname ? dupstr(hostname) : NULL; + + if (protocol) + disp->unixdomain = (!strcmp(protocol, "local") || + !strcmp(protocol, "unix")); + else + disp->unixdomain = platform_uses_x11_unix_by_default; + + if (!disp->hostname && !disp->unixdomain) + disp->hostname = dupstr("localhost"); + + disp->unixsocketpath = NULL; + + sfree(localcopy); + } + + /* + * Look up the display hostname, if we need to. + */ + if (disp->unixdomain) { + disp->addr = platform_get_x11_unix_address(disp->unixsocketpath, + disp->displaynum); + if (disp->unixsocketpath) + disp->realhost = dupstr(disp->unixsocketpath); + else + disp->realhost = dupprintf("unix:%d", disp->displaynum); + disp->port = 0; + } else { + const char *err; + + disp->port = 6000 + disp->displaynum; + disp->addr = name_lookup(disp->hostname, disp->port, + &disp->realhost, cfg, ADDRTYPE_UNSPEC); + + if ((err = sk_addr_error(disp->addr)) != NULL) { + sk_addr_free(disp->addr); + sfree(disp->hostname); + sfree(disp->unixsocketpath); + return NULL; /* FIXME: report an error */ + } + } + + /* + * Invent the remote authorisation details. + */ + if (authtype == X11_MIT) { + disp->remoteauthproto = X11_MIT; /* MIT-MAGIC-COOKIE-1. Cookie size is 128 bits (16 bytes). */ - auth->fakelen = 16; + disp->remoteauthdata = snewn(16, unsigned char); for (i = 0; i < 16; i++) - auth->fakedata[i] = random_byte(); - auth->xdmseen = NULL; + disp->remoteauthdata[i] = random_byte(); + disp->remoteauthdatalen = 16; + + disp->xdmseen = NULL; } else { - assert(proto_id == X11_XDM); - auth->fakeproto = X11_XDM; + assert(authtype == X11_XDM); + disp->remoteauthproto = X11_XDM; /* XDM-AUTHORIZATION-1. Cookie size is 16 bytes; byte 8 is zero. */ - auth->fakelen = 16; + disp->remoteauthdata = snewn(16, unsigned char); for (i = 0; i < 16; i++) - auth->fakedata[i] = (i == 8 ? 0 : random_byte()); - auth->xdmseen = newtree234(xdmseen_cmp); + disp->remoteauthdata[i] = (i == 8 ? 0 : random_byte()); + disp->remoteauthdatalen = 16; + + disp->xdmseen = newtree234(xdmseen_cmp); } + disp->remoteauthprotoname = dupstr(x11_authnames[disp->remoteauthproto]); + disp->remoteauthdatastring = snewn(disp->remoteauthdatalen * 2 + 1, char); + for (i = 0; i < disp->remoteauthdatalen; i++) + sprintf(disp->remoteauthdatastring + i*2, "%02x", + disp->remoteauthdata[i]); - /* Now format for the recipient. */ - strncpy(proto, x11_authnames[auth->fakeproto], protomaxlen); - ourdata[0] = '\0'; - for (i = 0; i < auth->fakelen; i++) - sprintf(ourdata + strlen(ourdata), "%02x", auth->fakedata[i]); - strncpy(data, ourdata, datamaxlen); + /* + * Fetch the local authorisation details. + */ + disp->localauthproto = X11_NO_AUTH; + disp->localauthdata = NULL; + disp->localauthdatalen = 0; + platform_get_x11_auth(disp, cfg); - return auth; + return disp; } -void x11_free_auth(void *authv) +void x11_free_display(struct X11Display *disp) { - struct X11Auth *auth = (struct X11Auth *)authv; - struct XDMSeen *seen; - - if (auth->xdmseen != NULL) { - while ((seen = delpos234(auth->xdmseen, 0)) != NULL) + if (disp->xdmseen != NULL) { + struct XDMSeen *seen; + while ((seen = delpos234(disp->xdmseen, 0)) != NULL) sfree(seen); - freetree234(auth->xdmseen); + freetree234(disp->xdmseen); } - sfree(auth); -} - -/* - * Fetch the real auth data for a given display string, and store - * it in an X11Auth structure. Returns NULL on success, or an error - * string. - */ -void x11_get_real_auth(void *authv, char *display) -{ - struct X11Auth *auth = (struct X11Auth *)authv; - - auth->realproto = X11_NO_AUTH; /* in case next call does nothing */ - - auth->reallen = sizeof(auth->realdata); - platform_get_x11_auth(display, &auth->realproto, - auth->realdata, &auth->reallen); + sfree(disp->hostname); + sfree(disp->unixsocketpath); + if (disp->localauthdata) + memset(disp->localauthdata, 0, disp->localauthdatalen); + sfree(disp->localauthdata); + if (disp->remoteauthdata) + memset(disp->remoteauthdata, 0, disp->remoteauthdatalen); + sfree(disp->remoteauthdata); + sfree(disp->remoteauthprotoname); + sfree(disp->remoteauthdatastring); + sk_addr_free(disp->addr); + sfree(disp); } #define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ static char *x11_verify(unsigned long peer_ip, int peer_port, - struct X11Auth *auth, char *proto, + struct X11Display *disp, char *proto, unsigned char *data, int dlen) { - if (strcmp(proto, x11_authnames[auth->fakeproto]) != 0) - return "wrong authentication protocol attempted"; - if (auth->fakeproto == X11_MIT) { - if (dlen != auth->fakelen) + if (strcmp(proto, x11_authnames[disp->remoteauthproto]) != 0) + return "wrong authorisation protocol attempted"; + if (disp->remoteauthproto == X11_MIT) { + if (dlen != disp->remoteauthdatalen) return "MIT-MAGIC-COOKIE-1 data was wrong length"; - if (memcmp(auth->fakedata, data, dlen) != 0) + if (memcmp(disp->remoteauthdata, data, dlen) != 0) return "MIT-MAGIC-COOKIE-1 data did not match"; } - if (auth->fakeproto == X11_XDM) { + if (disp->remoteauthproto == X11_XDM) { unsigned long t; time_t tim; int i; @@ -146,8 +250,8 @@ static char *x11_verify(unsigned long peer_ip, int peer_port, return "XDM-AUTHORIZATION-1 data was wrong length"; if (peer_port == -1) return "cannot do XDM-AUTHORIZATION-1 without remote address data"; - des_decrypt_xdmauth(auth->fakedata+9, data, 24); - if (memcmp(auth->fakedata, data, 8) != 0) + des_decrypt_xdmauth(disp->remoteauthdata+9, data, 24); + if (memcmp(disp->remoteauthdata, data, 8) != 0) return "XDM-AUTHORIZATION-1 data failed check"; /* cookie wrong */ if (GET_32BIT_MSB_FIRST(data+8) != peer_ip) return "XDM-AUTHORIZATION-1 data failed check"; /* IP wrong */ @@ -163,25 +267,144 @@ static char *x11_verify(unsigned long peer_ip, int peer_port, seen = snew(struct XDMSeen); seen->time = t; memcpy(seen->clientid, data+8, 6); - assert(auth->xdmseen != NULL); - ret = add234(auth->xdmseen, seen); + assert(disp->xdmseen != NULL); + ret = add234(disp->xdmseen, seen); if (ret != seen) { sfree(seen); return "XDM-AUTHORIZATION-1 data replayed"; } /* While we're here, purge entries too old to be replayed. */ for (;;) { - seen = index234(auth->xdmseen, 0); + seen = index234(disp->xdmseen, 0); assert(seen != NULL); if (t - seen->time <= XDM_MAXSKEW) break; - sfree(delpos234(auth->xdmseen, 0)); + sfree(delpos234(disp->xdmseen, 0)); } } /* implement other protocols here if ever required */ return NULL; } +void x11_get_auth_from_authfile(struct X11Display *disp, + const char *authfilename) +{ + FILE *authfp; + char *buf, *ptr, *str[4]; + int len[4]; + int family, protocol; + + authfp = fopen(authfilename, "rb"); + if (!authfp) + return; + + /* Records in .Xauthority contain four strings of up to 64K each */ + buf = snewn(65537 * 4, char); + + while (1) { + int c, i, j; + +#define GET do { c = fgetc(authfp); if (c == EOF) goto done; c = (unsigned char)c; } while (0) + /* Expect a big-endian 2-byte number giving address family */ + GET; family = c; + GET; family = (family << 8) | c; + /* Then expect four strings, each composed of a big-endian 2-byte + * length field followed by that many bytes of data */ + ptr = buf; + for (i = 0; i < 4; i++) { + GET; len[i] = c; + GET; len[i] = (len[i] << 8) | c; + str[i] = ptr; + for (j = 0; j < len[i]; j++) { + GET; *ptr++ = c; + } + *ptr++ = '\0'; + } +#undef GET + + /* + * Now we have a full X authority record in memory. See + * whether it matches the display we're trying to + * authenticate to. + * + * The details we've just read should be interpreted as + * follows: + * + * - 'family' is the network address family used to + * connect to the display. 0 means IPv4; 6 means IPv6; + * 256 means Unix-domain sockets. + * + * - str[0] is the network address itself. For IPv4 and + * IPv6, this is a string of binary data of the + * appropriate length (respectively 4 and 16 bytes) + * representing the address in big-endian format, e.g. + * 7F 00 00 01 means IPv4 localhost. For Unix-domain + * sockets, this is the host name of the machine on + * which the Unix-domain display resides (so that an + * .Xauthority file on a shared file system can contain + * authority entries for Unix-domain displays on + * several machines without them clashing). + * + * - str[1] is the display number. I've no idea why + * .Xauthority stores this as a string when it has a + * perfectly good integer format, but there we go. + * + * - str[2] is the authorisation method, encoded as its + * canonical string name (i.e. "MIT-MAGIC-COOKIE-1", + * "XDM-AUTHORIZATION-1" or something we don't + * recognise). + * + * - str[3] is the actual authorisation data, stored in + * binary form. + */ + + if (disp->displaynum < 0 || disp->displaynum != atoi(str[1])) + continue; /* not the one */ + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (!strcmp(str[2], x11_authnames[protocol])) + break; + if (protocol == lenof(x11_authnames)) + continue; /* don't recognise this protocol, look for another */ + + switch (family) { + case 0: + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { + char buf[4]; + sk_addrcopy(disp->addr, buf); + if (len[0] == 4 && !memcmp(str[0], buf, 4)) + goto found; + } + break; + case 6: + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { + char buf[16]; + sk_addrcopy(disp->addr, buf); + if (len[0] == 16 && !memcmp(str[0], buf, 16)) + goto found; + } + break; + case 256: + if (disp->unixdomain && !strcmp(disp->hostname, str[0])) + goto found; + break; + } + } + + found: + disp->localauthproto = protocol; + disp->localauthdata = snewn(len[3], unsigned char); + memcpy(disp->localauthdata, str[3], len[3]); + disp->localauthdatalen = len[3]; + + done: + fclose(authfp); + memset(buf, 0, 65537 * 4); + sfree(buf); +} + static void x11_log(Plug p, int type, SockAddr addr, int port, const char *error_msg, int error_code) { @@ -240,33 +463,15 @@ int x11_get_screen_number(char *display) return atoi(display + n + 1); } -/* Find the right display, returns an allocated string */ -char *x11_display(const char *display) { - char *ret; - if(!display || !*display) { - /* try to find platform-specific local display */ - if((ret = platform_get_x_display())==0 || !*ret) - /* plausible default for all platforms */ - ret = dupstr(":0"); - } else - ret = dupstr(display); - if(ret[0] == ':') { - /* no transport specified, use whatever we think is best */ - char *s = dupcat(platform_x11_best_transport, ret, (char *)0); - sfree(ret); - return s; - } else - return ret; -} - /* * Called to set up the raw connection. * * Returns an error message, or NULL on success. * also, fills the SocketsStructure */ -const char *x11_init(Socket * s, char *display, void *c, void *auth, - const char *peeraddr, int peerport, const Config *cfg) +extern const char *x11_init(Socket *s, struct X11Display *disp, void *c, + const char *peeraddr, int peerport, + const Config *cfg) { static const struct plug_function_table fn_table = { x11_log, @@ -276,62 +481,23 @@ const char *x11_init(Socket * s, char *display, void *c, void *auth, NULL }; - SockAddr addr; - int port; const char *err; - char *dummy_realhost; - char host[128]; - int n, displaynum; struct X11Private *pr; - /* default display */ - display = x11_display(display); - /* - * Split up display name into host and display-number parts. - */ - n = strcspn(display, ":"); - assert(n != 0); /* x11_display() promises this */ - if (display[n]) - displaynum = atoi(display + n + 1); - else - displaynum = 0; /* sensible default */ - if (n > sizeof(host) - 1) - n = sizeof(host) - 1; - strncpy(host, display, n); - host[n] = '\0'; - sfree(display); - - if(!strcmp(host, "unix") || host[0] == '/') { - /* use AF_UNIX sockets (doesn't make sense on all platforms) */ - addr = platform_get_x11_unix_address(display, displaynum, - &dummy_realhost); - port = 0; /* to show we are not confused */ - } else { - port = 6000 + displaynum; - - /* - * Try to find host. - */ - addr = name_lookup(host, port, &dummy_realhost, cfg, ADDRTYPE_UNSPEC); - if ((err = sk_addr_error(addr)) != NULL) { - sk_addr_free(addr); - return err; - } - } - /* * Open socket. */ pr = snew(struct X11Private); pr->fn = &fn_table; pr->auth_protocol = NULL; - pr->auth = (struct X11Auth *)auth; + pr->disp = disp; pr->verified = 0; pr->data_read = 0; pr->throttled = pr->throttle_override = 0; pr->c = c; - pr->s = *s = new_connection(addr, dummy_realhost, port, + pr->s = *s = new_connection(sk_addr_dup(disp->addr), + disp->realhost, disp->port, 0, 1, 0, 0, (Plug) pr, cfg); if ((err = sk_socket_error(*s)) != NULL) { sfree(pr); @@ -439,18 +605,18 @@ int x11_send(Socket s, char *data, int len) return 0; /* - * If we haven't verified the authentication, do so now. + * If we haven't verified the authorisation, do so now. */ if (!pr->verified) { char *err; pr->auth_protocol[pr->auth_plen] = '\0'; /* ASCIZ */ err = x11_verify(pr->peer_ip, pr->peer_port, - pr->auth, pr->auth_protocol, + pr->disp, pr->auth_protocol, pr->auth_data, pr->auth_dlen); /* - * If authentication failed, construct and send an error + * If authorisation failed, construct and send an error * packet, then terminate the connection. */ if (err) { @@ -484,27 +650,27 @@ int x11_send(Socket s, char *data, int len) { char realauthdata[64]; int realauthlen = 0; - int authstrlen = strlen(x11_authnames[pr->auth->realproto]); + int authstrlen = strlen(x11_authnames[pr->disp->localauthproto]); int buflen = 0; /* initialise to placate optimiser */ static const char zeroes[4] = { 0,0,0,0 }; void *buf; - if (pr->auth->realproto == X11_MIT) { - assert(pr->auth->reallen <= lenof(realauthdata)); - realauthlen = pr->auth->reallen; - memcpy(realauthdata, pr->auth->realdata, realauthlen); - } else if (pr->auth->realproto == X11_XDM && - pr->auth->reallen == 16 && + if (pr->disp->localauthproto == X11_MIT) { + assert(pr->disp->localauthdatalen <= lenof(realauthdata)); + realauthlen = pr->disp->localauthdatalen; + memcpy(realauthdata, pr->disp->localauthdata, realauthlen); + } else if (pr->disp->localauthproto == X11_XDM && + pr->disp->localauthdatalen == 16 && ((buf = sk_getxdmdata(s, &buflen))!=0)) { time_t t; realauthlen = (buflen+12+7) & ~7; assert(realauthlen <= lenof(realauthdata)); memset(realauthdata, 0, realauthlen); - memcpy(realauthdata, pr->auth->realdata, 8); + memcpy(realauthdata, pr->disp->localauthdata, 8); memcpy(realauthdata+8, buf, buflen); t = time(NULL); PUT_32BIT_MSB_FIRST(realauthdata+8+buflen, t); - des_encrypt_xdmauth(pr->auth->realdata+9, + des_encrypt_xdmauth(pr->disp->localauthdata+9, (unsigned char *)realauthdata, realauthlen); sfree(buf); @@ -517,7 +683,8 @@ int x11_send(Socket s, char *data, int len) sk_write(s, (char *)pr->firstpkt, 12); if (authstrlen) { - sk_write(s, x11_authnames[pr->auth->realproto], authstrlen); + sk_write(s, x11_authnames[pr->disp->localauthproto], + authstrlen); sk_write(s, zeroes, 3 & (-authstrlen)); } if (realauthlen) { -- 2.11.0