1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 17:38:00 +00:00

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.

[originally from svn r8305]
This commit is contained in:
Simon Tatham 2008-11-17 18:38:09 +00:00
parent 0cef8a897d
commit ca6fc3a4da
15 changed files with 534 additions and 313 deletions

4
Recipe
View File

@ -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

View File

@ -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}

View File

@ -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) */

View File

@ -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);

50
ssh.c
View File

@ -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);

88
ssh.h
View File

@ -2,6 +2,7 @@
#include <string.h>
#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);

View File

@ -5,117 +5,66 @@
#include <ctype.h>
#include <unistd.h>
#include <assert.h>
#include <stdlib.h>
#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;
/* Skip white space. */
while (c != EOF && c != '\n' && isspace(c))
c = getc(fp);
if (c == EOF) break;
if (c == '\n') continue;
/*
* 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);
}
*datalen = i;
*protocol = proto;
memcpy(data, localbuf, i);
/* Nonetheless, continue looping round; we might find a better one. */
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;
}
/*
* 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;
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);
}
/*
* 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;
}
}
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;

View File

@ -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

View File

@ -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.

View File

@ -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)));
}

View File

@ -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"

View File

@ -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"));

View File

@ -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.
*/

18
windows/winx11.c Normal file
View File

@ -0,0 +1,18 @@
/*
* winx11.c: fetch local auth data for X forwarding.
*/
#include <ctype.h>
#include <assert.h>
#include <stdlib.h>
#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;

451
x11fwd.c
View File

@ -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) {