diff --git a/CMakeLists.txt b/CMakeLists.txt index b1050f2d..1047773e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,6 +52,12 @@ add_library(utils STATIC utils/wcwidth.c utils/wildcard.c utils/write_c_string_literal.c + utils/x11authfile.c + utils/x11authnames.c + utils/x11_dehexify.c + utils/x11_identify_auth_proto.c + utils/x11_make_greeting.c + utils/x11_parse_ip.c ${GENERATED_COMMIT_C}) add_library(logging OBJECT @@ -78,7 +84,7 @@ add_library(crypto STATIC cproxy.c) add_library(network STATIC - be_misc.c nullplug.c errsock.c proxy.c logging.c) + be_misc.c nullplug.c errsock.c proxy.c logging.c x11disp.c) add_library(keygen STATIC millerrabin.c mpunsafe.c pockle.c primecandidate.c smallprimes.c diff --git a/misc.h b/misc.h index 7b4012b6..04fe3e68 100644 --- a/misc.h +++ b/misc.h @@ -374,6 +374,22 @@ static inline void PUT_16BIT_MSB_FIRST(void *vp, uint16_t value) p[0] = (uint8_t)(value >> 8); } +/* For use in X11-related applications, an endianness-variable form of + * {GET,PUT}_16BIT which expects 'endian' to be either 'B' or 'l' */ + +static inline uint16_t GET_16BIT_X11(char endian, const void *p) +{ + return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p); +} + +static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value) +{ + if (endian == 'B') + PUT_16BIT_MSB_FIRST(p, value); + else + PUT_16BIT_LSB_FIRST(p, value); +} + /* Replace NULL with the empty string, permitting an idiom in which we * get a string (pointer,length) pair that might be NULL,0 and can * then safely say things like printf("%.*s", length, NULLTOEMPTY(ptr)) */ diff --git a/putty.h b/putty.h index a73817af..a14da123 100644 --- a/putty.h +++ b/putty.h @@ -2205,7 +2205,7 @@ enum { X11_XDM, /* XDM-AUTHORIZATION-1 */ X11_NAUTHS }; -extern const char *const x11_authnames[]; /* declared in x11fwd.c */ +extern const char *const x11_authnames[X11_NAUTHS]; /* * An enum for the copy-paste UI action configuration. diff --git a/ssh.h b/ssh.h index 08c6797c..fbacbfa4 100644 --- a/ssh.h +++ b/ssh.h @@ -1191,6 +1191,7 @@ void x11_format_auth_for_authfile( ptrlen authproto, ptrlen authdata); int x11_identify_auth_proto(ptrlen protoname); void *x11_dehexify(ptrlen hex, int *outlen); +bool x11_parse_ip(const char *addr_string, unsigned long *ip); Channel *agentf_new(SshChannel *c); diff --git a/unix/CMakeLists.txt b/unix/CMakeLists.txt index 72bf507c..d77f7c9d 100644 --- a/unix/CMakeLists.txt +++ b/unix/CMakeLists.txt @@ -31,9 +31,9 @@ add_platform_sources_to_library(console add_platform_sources_to_library(settings uxstore.c) add_platform_sources_to_library(network - uxnet.c uxfdsock.c uxagentsock.c uxpeer.c uxproxy.c) + uxnet.c uxfdsock.c uxagentsock.c uxpeer.c uxproxy.c ux_x11.c) add_platform_sources_to_library(sshcommon - uxnoise.c ux_x11.c) + uxnoise.c) add_platform_sources_to_library(sshclient uxgss.c uxagentc.c uxshare.c) add_platform_sources_to_library(sshserver diff --git a/utils/x11_dehexify.c b/utils/x11_dehexify.c new file mode 100644 index 00000000..13ffb0d0 --- /dev/null +++ b/utils/x11_dehexify.c @@ -0,0 +1,28 @@ +/* + * Utility function to convert a textual representation of an X11 + * auth hex cookie into binary auth data. + */ + +#include "putty.h" + +void *x11_dehexify(ptrlen hexpl, int *outlen) +{ + int len, i; + unsigned char *ret; + + len = hexpl.len / 2; + ret = snewn(len, unsigned char); + + for (i = 0; i < len; i++) { + char bytestr[3]; + unsigned val = 0; + bytestr[0] = ((const char *)hexpl.ptr)[2*i]; + bytestr[1] = ((const char *)hexpl.ptr)[2*i+1]; + bytestr[2] = '\0'; + sscanf(bytestr, "%x", &val); + ret[i] = val; + } + + *outlen = len; + return ret; +} diff --git a/utils/x11_identify_auth_proto.c b/utils/x11_identify_auth_proto.c new file mode 100644 index 00000000..14075a3f --- /dev/null +++ b/utils/x11_identify_auth_proto.c @@ -0,0 +1,16 @@ +/* + * Utility function to convert a textual representation of an X11 + * auth protocol name into our integer protocol ids. + */ + +#include "putty.h" + +int x11_identify_auth_proto(ptrlen protoname) +{ + int protocol; + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (ptrlen_eq_string(protoname, x11_authnames[protocol])) + return protocol; + return -1; +} diff --git a/utils/x11_make_greeting.c b/utils/x11_make_greeting.c new file mode 100644 index 00000000..4efd8e83 --- /dev/null +++ b/utils/x11_make_greeting.c @@ -0,0 +1,67 @@ +/* + * Construct an X11 greeting packet, including making up the right + * authorisation data. + */ + +#include "putty.h" +#include "ssh.h" + +void *x11_make_greeting(int endian, int protomajor, int protominor, + int auth_proto, const void *auth_data, int auth_len, + const char *peer_addr, int peer_port, + int *outlen) +{ + unsigned char *greeting; + unsigned char realauthdata[64]; + const char *authname; + const unsigned char *authdata; + int authnamelen, authnamelen_pad; + int authdatalen, authdatalen_pad; + int greeting_len; + + authname = x11_authnames[auth_proto]; + authnamelen = strlen(authname); + authnamelen_pad = (authnamelen + 3) & ~3; + + if (auth_proto == X11_MIT) { + authdata = auth_data; + authdatalen = auth_len; + } else if (auth_proto == X11_XDM && auth_len == 16) { + time_t t; + unsigned long peer_ip = 0; + + x11_parse_ip(peer_addr, &peer_ip); + + authdata = realauthdata; + authdatalen = 24; + memset(realauthdata, 0, authdatalen); + memcpy(realauthdata, auth_data, 8); + PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); + PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); + t = time(NULL); + PUT_32BIT_MSB_FIRST(realauthdata+14, t); + + des_encrypt_xdmauth((char *)auth_data + 9, realauthdata, authdatalen); + } else { + authdata = realauthdata; + authdatalen = 0; + } + + authdatalen_pad = (authdatalen + 3) & ~3; + greeting_len = 12 + authnamelen_pad + authdatalen_pad; + + greeting = snewn(greeting_len, unsigned char); + memset(greeting, 0, greeting_len); + greeting[0] = endian; + PUT_16BIT_X11(endian, greeting+2, protomajor); + PUT_16BIT_X11(endian, greeting+4, protominor); + PUT_16BIT_X11(endian, greeting+6, authnamelen); + PUT_16BIT_X11(endian, greeting+8, authdatalen); + memcpy(greeting+12, authname, authnamelen); + memcpy(greeting+12+authnamelen_pad, authdata, authdatalen); + + smemclr(realauthdata, sizeof(realauthdata)); + + *outlen = greeting_len; + return greeting; +} diff --git a/utils/x11_parse_ip.c b/utils/x11_parse_ip.c new file mode 100644 index 00000000..12649b49 --- /dev/null +++ b/utils/x11_parse_ip.c @@ -0,0 +1,20 @@ +/* + * Try to make sense of a string as an IPv4 address, for + * XDM-AUTHORIZATION-1 purposes. + */ + +#include + +#include "putty.h" + +bool x11_parse_ip(const char *addr_string, unsigned long *ip) +{ + int i[4]; + if (addr_string && + 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { + *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; + return true; + } else { + return false; + } +} diff --git a/utils/x11authfile.c b/utils/x11authfile.c new file mode 100644 index 00000000..4fc84ab5 --- /dev/null +++ b/utils/x11authfile.c @@ -0,0 +1,244 @@ +/* + * Functions to handle .Xauthority files. + */ + +#include +#include +#include + +#include "putty.h" +#include "ssh.h" + +ptrlen BinarySource_get_string_xauth(BinarySource *src) +{ + size_t len = get_uint16(src); + return get_data(src, len); +} +#define get_string_xauth(src) \ + BinarySource_get_string_xauth(BinarySource_UPCAST(src)) + +void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) +{ + assert((pl.len >> 16) == 0); + put_uint16(bs, pl.len); + put_datapl(bs, pl); +} +#define put_stringpl_xauth(bs, ptrlen) \ + BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen) + +void x11_get_auth_from_authfile(struct X11Display *disp, + const char *authfilename) +{ + FILE *authfp; + char *buf; + int size; + BinarySource src[1]; + int family, protocol; + ptrlen addr, protoname, data; + char *displaynum_string; + int displaynum; + bool ideal_match = false; + char *ourhostname; + + /* A maximally sized (wildly implausible) .Xauthority record + * consists of a 16-bit integer to start with, then four strings, + * each of which has a 16-bit length field followed by that many + * bytes of data (i.e. up to 0xFFFF bytes). */ + const size_t MAX_RECORD_SIZE = 2 + 4 * (2+0xFFFF); + + /* We'll want a buffer of twice that size (see below). */ + const size_t BUF_SIZE = 2 * MAX_RECORD_SIZE; + + /* + * Normally we should look for precisely the details specified in + * `disp'. However, there's an oddity when the display is local: + * displays like "localhost:0" usually have their details stored + * in a Unix-domain-socket record (even if there isn't actually a + * real Unix-domain socket available, as with OpenSSH's proxy X11 + * server). + * + * This is apparently a fudge to get round the meaninglessness of + * "localhost" in a shared-home-directory context -- xauth entries + * for Unix-domain sockets already disambiguate this by storing + * the *local* hostname in the conveniently-blank hostname field, + * but IP "localhost" records couldn't do this. So, typically, an + * IP "localhost" entry in the auth database isn't present and if + * it were it would be ignored. + * + * However, we don't entirely trust that (say) Windows X servers + * won't rely on a straight "localhost" entry, bad idea though + * that is; so if we can't find a Unix-domain-socket entry we'll + * fall back to an IP-based entry if we can find one. + */ + bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); + + authfp = fopen(authfilename, "rb"); + if (!authfp) + return; + + ourhostname = get_hostname(); + + /* + * Allocate enough space to hold two maximally sized records, so + * that a full record can start anywhere in the first half. That + * way we avoid the accidentally-quadratic algorithm that would + * arise if we moved everything to the front of the buffer after + * consuming each record; instead, we only move everything to the + * front after our current position gets past the half-way mark. + * Before then, there's no need to move anyway; so this guarantees + * linear time, in that every byte written into this buffer moves + * at most once (because every move is from the second half of the + * buffer to the first half). + */ + buf = snewn(BUF_SIZE, char); + size = fread(buf, 1, BUF_SIZE, authfp); + BinarySource_BARE_INIT(src, buf, size); + + while (!ideal_match) { + bool match = false; + + if (src->pos >= MAX_RECORD_SIZE) { + size -= src->pos; + memcpy(buf, buf + src->pos, size); + size += fread(buf + size, 1, BUF_SIZE - size, authfp); + BinarySource_BARE_INIT(src, buf, size); + } + + family = get_uint16(src); + addr = get_string_xauth(src); + displaynum_string = mkstr(get_string_xauth(src)); + displaynum = displaynum_string[0] ? atoi(displaynum_string) : -1; + sfree(displaynum_string); + protoname = get_string_xauth(src); + data = get_string_xauth(src); + if (get_err(src)) + break; + + /* + * 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. + * + * - 'addr' 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). + * + * - 'displaynum' is the display number. An empty display + * number is a wildcard for any display number. + * + * - 'protoname' is the authorisation protocol, encoded as + * its canonical string name (i.e. "MIT-MAGIC-COOKIE-1", + * "XDM-AUTHORIZATION-1" or something we don't recognise). + * + * - 'data' is the actual authorisation data, stored in + * binary form. + */ + + if (disp->displaynum < 0 || + (displaynum >= 0 && disp->displaynum != displaynum)) + continue; /* not the one */ + + for (protocol = 1; protocol < lenof(x11_authnames); protocol++) + if (ptrlen_eq_string(protoname, x11_authnames[protocol])) + break; + if (protocol == lenof(x11_authnames)) + continue; /* don't recognise this protocol, look for another */ + + switch (family) { + case 0: /* IPv4 */ + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { + char buf[4]; + sk_addrcopy(disp->addr, buf); + if (addr.len == 4 && !memcmp(addr.ptr, buf, 4)) { + match = true; + /* If this is a "localhost" entry, note it down + * but carry on looking for a Unix-domain entry. */ + ideal_match = !localhost; + } + } + break; + case 6: /* IPv6 */ + if (!disp->unixdomain && + sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { + char buf[16]; + sk_addrcopy(disp->addr, buf); + if (addr.len == 16 && !memcmp(addr.ptr, buf, 16)) { + match = true; + ideal_match = !localhost; + } + } + break; + case 256: /* Unix-domain / localhost */ + if ((disp->unixdomain || localhost) + && ourhostname && ptrlen_eq_string(addr, ourhostname)) { + /* A matching Unix-domain socket is always the best + * match. */ + match = true; + ideal_match = true; + } + break; + } + + if (match) { + /* Current best guess -- may be overridden if !ideal_match */ + disp->localauthproto = protocol; + sfree(disp->localauthdata); /* free previous guess, if any */ + disp->localauthdata = snewn(data.len, unsigned char); + memcpy(disp->localauthdata, data.ptr, data.len); + disp->localauthdatalen = data.len; + } + } + + fclose(authfp); + smemclr(buf, 2 * MAX_RECORD_SIZE); + sfree(buf); + sfree(ourhostname); +} + +void x11_format_auth_for_authfile( + BinarySink *bs, SockAddr *addr, int display_no, + ptrlen authproto, ptrlen authdata) +{ + if (sk_address_is_special_local(addr)) { + char *ourhostname = get_hostname(); + put_uint16(bs, 256); /* indicates Unix-domain socket */ + put_stringpl_xauth(bs, ptrlen_from_asciz(ourhostname)); + sfree(ourhostname); + } else if (sk_addrtype(addr) == ADDRTYPE_IPV4) { + char ipv4buf[4]; + sk_addrcopy(addr, ipv4buf); + put_uint16(bs, 0); /* indicates IPv4 */ + put_stringpl_xauth(bs, make_ptrlen(ipv4buf, 4)); + } else if (sk_addrtype(addr) == ADDRTYPE_IPV6) { + char ipv6buf[16]; + sk_addrcopy(addr, ipv6buf); + put_uint16(bs, 6); /* indicates IPv6 */ + put_stringpl_xauth(bs, make_ptrlen(ipv6buf, 16)); + } else { + unreachable("Bad address type in x11_format_auth_for_authfile"); + } + + { + char *numberbuf = dupprintf("%d", display_no); + put_stringpl_xauth(bs, ptrlen_from_asciz(numberbuf)); + sfree(numberbuf); + } + + put_stringpl_xauth(bs, authproto); + put_stringpl_xauth(bs, authdata); +} diff --git a/utils/x11authnames.c b/utils/x11authnames.c new file mode 100644 index 00000000..ef3c2673 --- /dev/null +++ b/utils/x11authnames.c @@ -0,0 +1,9 @@ +/* + * Definition of the array of X11 authorisation method names. + */ + +#include "putty.h" + +const char *const x11_authnames[X11_NAUTHS] = { + "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" +}; diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index ff6805f0..41b617ab 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -40,9 +40,9 @@ add_platform_sources_to_library(console add_platform_sources_to_library(settings winstore.c) add_platform_sources_to_library(network - winnet.c winhsock.c winnpc.c winnps.c winproxy.c) + winnet.c winhsock.c winnpc.c winnps.c winproxy.c winx11.c) add_platform_sources_to_library(sshcommon - winnoise.c winx11.c) + winnoise.c) add_platform_sources_to_library(sshclient winpgntc.c wingss.c winshare.c) add_platform_sources_to_library(sftpclient diff --git a/x11disp.c b/x11disp.c new file mode 100644 index 00000000..58ffc1e6 --- /dev/null +++ b/x11disp.c @@ -0,0 +1,189 @@ +/* + * Functions to manage an X11Display structure, by creating one from + * an ordinary display name string, and freeing one. + */ + +#include +#include +#include +#include + +#include "putty.h" +#include "ssh.h" +#include "sshchan.h" +#include "tree234.h" + +struct X11Display *x11_setup_display(const char *display, Conf *conf, + char **error_msg) +{ + struct X11Display *disp = snew(struct X11Display); + char *localcopy; + + *error_msg = NULL; + + 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; + disp->addr = NULL; + } else { + char *colon, *dot, *slash; + char *protocol, *hostname; + + colon = host_strrchr(localcopy, ':'); + if (!colon) { + *error_msg = dupprintf("display name '%s' has no ':number'" + " suffix", localcopy); + + sfree(disp); + sfree(localcopy); + return NULL; + } + + *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 if (!*hostname || !strcmp(hostname, "unix")) + disp->unixdomain = platform_uses_x11_unix_by_default; + else + disp->unixdomain = false; + + if (!disp->hostname && !disp->unixdomain) + disp->hostname = dupstr("localhost"); + + disp->unixsocketpath = NULL; + disp->addr = NULL; + + sfree(localcopy); + } + + /* + * Look up the display hostname, if we need to. + */ + if (!disp->unixdomain) { + const char *err; + + disp->port = 6000 + disp->displaynum; + disp->addr = name_lookup(disp->hostname, disp->port, + &disp->realhost, conf, ADDRTYPE_UNSPEC, + NULL, NULL); + + if ((err = sk_addr_error(disp->addr)) != NULL) { + *error_msg = dupprintf("unable to resolve host name '%s' in " + "display name", disp->hostname); + + sk_addr_free(disp->addr); + sfree(disp->hostname); + sfree(disp->unixsocketpath); + sfree(disp); + return NULL; + } + } + + /* + * Try upgrading an IP-style localhost display to a Unix-socket + * display (as the standard X connection libraries do). + */ + if (!disp->unixdomain && sk_address_is_local(disp->addr)) { + SockAddr *ux = platform_get_x11_unix_address(NULL, disp->displaynum); + const char *err = sk_addr_error(ux); + if (!err) { + /* Create trial connection to see if there is a useful Unix-domain + * socket */ + Socket *s = sk_new(sk_addr_dup(ux), 0, false, false, + false, false, nullplug); + err = sk_socket_error(s); + sk_close(s); + } + if (err) { + sk_addr_free(ux); + } else { + sk_addr_free(disp->addr); + disp->unixdomain = true; + disp->addr = ux; + /* Fill in the rest in a moment */ + } + } + + if (disp->unixdomain) { + if (!disp->addr) + 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; + } + + /* + * Fetch the local authorisation details. + */ + disp->localauthproto = X11_NO_AUTH; + disp->localauthdata = NULL; + disp->localauthdatalen = 0; + platform_get_x11_auth(disp, conf); + + return disp; +} + +void x11_free_display(struct X11Display *disp) +{ + sfree(disp->hostname); + sfree(disp->unixsocketpath); + if (disp->localauthdata) + smemclr(disp->localauthdata, disp->localauthdatalen); + sfree(disp->localauthdata); + sk_addr_free(disp->addr); + sfree(disp); +} diff --git a/x11fwd.c b/x11fwd.c index 86f85831..a5066dd1 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -12,23 +12,6 @@ #include "sshchan.h" #include "tree234.h" -static inline uint16_t GET_16BIT_X11(char endian, const void *p) -{ - return endian == 'B' ? GET_16BIT_MSB_FIRST(p) : GET_16BIT_LSB_FIRST(p); -} - -static inline void PUT_16BIT_X11(char endian, void *p, uint16_t value) -{ - if (endian == 'B') - PUT_16BIT_MSB_FIRST(p, value); - else - PUT_16BIT_LSB_FIRST(p, value); -} - -const char *const x11_authnames[] = { - "", "MIT-MAGIC-COOKIE-1", "XDM-AUTHORIZATION-1" -}; - struct XDMSeen { unsigned int time; unsigned char clientid[6]; @@ -183,181 +166,6 @@ int x11_authcmp(void *av, void *bv) } } -struct X11Display *x11_setup_display(const char *display, Conf *conf, - char **error_msg) -{ - struct X11Display *disp = snew(struct X11Display); - char *localcopy; - - *error_msg = NULL; - - 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; - disp->addr = NULL; - } else { - char *colon, *dot, *slash; - char *protocol, *hostname; - - colon = host_strrchr(localcopy, ':'); - if (!colon) { - *error_msg = dupprintf("display name '%s' has no ':number'" - " suffix", localcopy); - - sfree(disp); - sfree(localcopy); - return NULL; - } - - *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 if (!*hostname || !strcmp(hostname, "unix")) - disp->unixdomain = platform_uses_x11_unix_by_default; - else - disp->unixdomain = false; - - if (!disp->hostname && !disp->unixdomain) - disp->hostname = dupstr("localhost"); - - disp->unixsocketpath = NULL; - disp->addr = NULL; - - sfree(localcopy); - } - - /* - * Look up the display hostname, if we need to. - */ - if (!disp->unixdomain) { - const char *err; - - disp->port = 6000 + disp->displaynum; - disp->addr = name_lookup(disp->hostname, disp->port, - &disp->realhost, conf, ADDRTYPE_UNSPEC, - NULL, NULL); - - if ((err = sk_addr_error(disp->addr)) != NULL) { - *error_msg = dupprintf("unable to resolve host name '%s' in " - "display name", disp->hostname); - - sk_addr_free(disp->addr); - sfree(disp->hostname); - sfree(disp->unixsocketpath); - sfree(disp); - return NULL; - } - } - - /* - * Try upgrading an IP-style localhost display to a Unix-socket - * display (as the standard X connection libraries do). - */ - if (!disp->unixdomain && sk_address_is_local(disp->addr)) { - SockAddr *ux = platform_get_x11_unix_address(NULL, disp->displaynum); - const char *err = sk_addr_error(ux); - if (!err) { - /* Create trial connection to see if there is a useful Unix-domain - * socket */ - Socket *s = sk_new(sk_addr_dup(ux), 0, false, false, - false, false, nullplug); - err = sk_socket_error(s); - sk_close(s); - } - if (err) { - sk_addr_free(ux); - } else { - sk_addr_free(disp->addr); - disp->unixdomain = true; - disp->addr = ux; - /* Fill in the rest in a moment */ - } - } - - if (disp->unixdomain) { - if (!disp->addr) - 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; - } - - /* - * Fetch the local authorisation details. - */ - disp->localauthproto = X11_NO_AUTH; - disp->localauthdata = NULL; - disp->localauthdatalen = 0; - platform_get_x11_auth(disp, conf); - - return disp; -} - -void x11_free_display(struct X11Display *disp) -{ - sfree(disp->hostname); - sfree(disp->unixsocketpath); - if (disp->localauthdata) - smemclr(disp->localauthdata, disp->localauthdatalen); - sfree(disp->localauthdata); - sk_addr_free(disp->addr); - sfree(disp); -} - #define XDM_MAXSKEW 20*60 /* 20 minute clock skew should be OK */ static const char *x11_verify(unsigned long peer_ip, int peer_port, @@ -450,240 +258,6 @@ static const char *x11_verify(unsigned long peer_ip, int peer_port, return NULL; } -ptrlen BinarySource_get_string_xauth(BinarySource *src) -{ - size_t len = get_uint16(src); - return get_data(src, len); -} -#define get_string_xauth(src) \ - BinarySource_get_string_xauth(BinarySource_UPCAST(src)) - -void BinarySink_put_stringpl_xauth(BinarySink *bs, ptrlen pl) -{ - assert((pl.len >> 16) == 0); - put_uint16(bs, pl.len); - put_datapl(bs, pl); -} -#define put_stringpl_xauth(bs, ptrlen) \ - BinarySink_put_stringpl_xauth(BinarySink_UPCAST(bs),ptrlen) - -void x11_get_auth_from_authfile(struct X11Display *disp, - const char *authfilename) -{ - FILE *authfp; - char *buf; - int size; - BinarySource src[1]; - int family, protocol; - ptrlen addr, protoname, data; - char *displaynum_string; - int displaynum; - bool ideal_match = false; - char *ourhostname; - - /* A maximally sized (wildly implausible) .Xauthority record - * consists of a 16-bit integer to start with, then four strings, - * each of which has a 16-bit length field followed by that many - * bytes of data (i.e. up to 0xFFFF bytes). */ - const size_t MAX_RECORD_SIZE = 2 + 4 * (2+0xFFFF); - - /* We'll want a buffer of twice that size (see below). */ - const size_t BUF_SIZE = 2 * MAX_RECORD_SIZE; - - /* - * Normally we should look for precisely the details specified in - * `disp'. However, there's an oddity when the display is local: - * displays like "localhost:0" usually have their details stored - * in a Unix-domain-socket record (even if there isn't actually a - * real Unix-domain socket available, as with OpenSSH's proxy X11 - * server). - * - * This is apparently a fudge to get round the meaninglessness of - * "localhost" in a shared-home-directory context -- xauth entries - * for Unix-domain sockets already disambiguate this by storing - * the *local* hostname in the conveniently-blank hostname field, - * but IP "localhost" records couldn't do this. So, typically, an - * IP "localhost" entry in the auth database isn't present and if - * it were it would be ignored. - * - * However, we don't entirely trust that (say) Windows X servers - * won't rely on a straight "localhost" entry, bad idea though - * that is; so if we can't find a Unix-domain-socket entry we'll - * fall back to an IP-based entry if we can find one. - */ - bool localhost = !disp->unixdomain && sk_address_is_local(disp->addr); - - authfp = fopen(authfilename, "rb"); - if (!authfp) - return; - - ourhostname = get_hostname(); - - /* - * Allocate enough space to hold two maximally sized records, so - * that a full record can start anywhere in the first half. That - * way we avoid the accidentally-quadratic algorithm that would - * arise if we moved everything to the front of the buffer after - * consuming each record; instead, we only move everything to the - * front after our current position gets past the half-way mark. - * Before then, there's no need to move anyway; so this guarantees - * linear time, in that every byte written into this buffer moves - * at most once (because every move is from the second half of the - * buffer to the first half). - */ - buf = snewn(BUF_SIZE, char); - size = fread(buf, 1, BUF_SIZE, authfp); - BinarySource_BARE_INIT(src, buf, size); - - while (!ideal_match) { - bool match = false; - - if (src->pos >= MAX_RECORD_SIZE) { - size -= src->pos; - memcpy(buf, buf + src->pos, size); - size += fread(buf + size, 1, BUF_SIZE - size, authfp); - BinarySource_BARE_INIT(src, buf, size); - } - - family = get_uint16(src); - addr = get_string_xauth(src); - displaynum_string = mkstr(get_string_xauth(src)); - displaynum = displaynum_string[0] ? atoi(displaynum_string) : -1; - sfree(displaynum_string); - protoname = get_string_xauth(src); - data = get_string_xauth(src); - if (get_err(src)) - break; - - /* - * 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. - * - * - 'addr' 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). - * - * - 'displaynum' is the display number. An empty display - * number is a wildcard for any display number. - * - * - 'protoname' is the authorisation protocol, encoded as - * its canonical string name (i.e. "MIT-MAGIC-COOKIE-1", - * "XDM-AUTHORIZATION-1" or something we don't recognise). - * - * - 'data' is the actual authorisation data, stored in - * binary form. - */ - - if (disp->displaynum < 0 || - (displaynum >= 0 && disp->displaynum != displaynum)) - continue; /* not the one */ - - for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (ptrlen_eq_string(protoname, x11_authnames[protocol])) - break; - if (protocol == lenof(x11_authnames)) - continue; /* don't recognise this protocol, look for another */ - - switch (family) { - case 0: /* IPv4 */ - if (!disp->unixdomain && - sk_addrtype(disp->addr) == ADDRTYPE_IPV4) { - char buf[4]; - sk_addrcopy(disp->addr, buf); - if (addr.len == 4 && !memcmp(addr.ptr, buf, 4)) { - match = true; - /* If this is a "localhost" entry, note it down - * but carry on looking for a Unix-domain entry. */ - ideal_match = !localhost; - } - } - break; - case 6: /* IPv6 */ - if (!disp->unixdomain && - sk_addrtype(disp->addr) == ADDRTYPE_IPV6) { - char buf[16]; - sk_addrcopy(disp->addr, buf); - if (addr.len == 16 && !memcmp(addr.ptr, buf, 16)) { - match = true; - ideal_match = !localhost; - } - } - break; - case 256: /* Unix-domain / localhost */ - if ((disp->unixdomain || localhost) - && ourhostname && ptrlen_eq_string(addr, ourhostname)) { - /* A matching Unix-domain socket is always the best - * match. */ - match = true; - ideal_match = true; - } - break; - } - - if (match) { - /* Current best guess -- may be overridden if !ideal_match */ - disp->localauthproto = protocol; - sfree(disp->localauthdata); /* free previous guess, if any */ - disp->localauthdata = snewn(data.len, unsigned char); - memcpy(disp->localauthdata, data.ptr, data.len); - disp->localauthdatalen = data.len; - } - } - - fclose(authfp); - smemclr(buf, 2 * MAX_RECORD_SIZE); - sfree(buf); - sfree(ourhostname); -} - -void x11_format_auth_for_authfile( - BinarySink *bs, SockAddr *addr, int display_no, - ptrlen authproto, ptrlen authdata) -{ - if (sk_address_is_special_local(addr)) { - char *ourhostname = get_hostname(); - put_uint16(bs, 256); /* indicates Unix-domain socket */ - put_stringpl_xauth(bs, ptrlen_from_asciz(ourhostname)); - sfree(ourhostname); - } else if (sk_addrtype(addr) == ADDRTYPE_IPV4) { - char ipv4buf[4]; - sk_addrcopy(addr, ipv4buf); - put_uint16(bs, 0); /* indicates IPv4 */ - put_stringpl_xauth(bs, make_ptrlen(ipv4buf, 4)); - } else if (sk_addrtype(addr) == ADDRTYPE_IPV6) { - char ipv6buf[16]; - sk_addrcopy(addr, ipv6buf); - put_uint16(bs, 6); /* indicates IPv6 */ - put_stringpl_xauth(bs, make_ptrlen(ipv6buf, 16)); - } else { - unreachable("Bad address type in x11_format_auth_for_authfile"); - } - - { - char *numberbuf = dupprintf("%d", display_no); - put_stringpl_xauth(bs, ptrlen_from_asciz(numberbuf)); - sfree(numberbuf); - } - - put_stringpl_xauth(bs, authproto); - put_stringpl_xauth(bs, authdata); -} - static void x11_log(Plug *p, PlugLogType type, SockAddr *addr, int port, const char *error_msg, int error_code) { @@ -743,24 +317,6 @@ static void x11_sent(Plug *plug, size_t bufsize) sshfwd_unthrottle(xconn->c, bufsize); } -/* - * When setting up X forwarding, we should send the screen number - * from the specified local display. This function extracts it from - * the display string. - */ -int x11_get_screen_number(char *display) -{ - int n; - - n = host_strcspn(display, ":"); - if (!display[n]) - return 0; - n = strcspn(display, "."); - if (!display[n]) - return 0; - return atoi(display + n + 1); -} - static const PlugVtable X11Connection_plugvt = { .log = x11_log, .closing = x11_closing, @@ -898,23 +454,6 @@ static void x11_send_init_error(struct X11Connection *xconn, sfree(full_message); } -static bool x11_parse_ip(const char *addr_string, unsigned long *ip) -{ - - /* - * See if we can make sense of this string as an IPv4 address, for - * XDM-AUTHORIZATION-1 purposes. - */ - int i[4]; - if (addr_string && - 4 == sscanf(addr_string, "%d.%d.%d.%d", i+0, i+1, i+2, i+3)) { - *ip = (i[0] << 24) | (i[1] << 16) | (i[2] << 8) | i[3]; - return true; - } else { - return false; - } -} - /* * Called to send data down the raw connection. */ @@ -1098,104 +637,3 @@ static char *x11_log_close_msg(Channel *chan) { return dupstr("Forwarded X11 connection terminated"); } - -/* - * 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(ptrlen protoname) -{ - int protocol; - - for (protocol = 1; protocol < lenof(x11_authnames); protocol++) - if (ptrlen_eq_string(protoname, x11_authnames[protocol])) - return protocol; - return -1; -} - -void *x11_dehexify(ptrlen hexpl, int *outlen) -{ - int len, i; - unsigned char *ret; - - len = hexpl.len / 2; - ret = snewn(len, unsigned char); - - for (i = 0; i < len; i++) { - char bytestr[3]; - unsigned val = 0; - bytestr[0] = ((const char *)hexpl.ptr)[2*i]; - bytestr[1] = ((const char *)hexpl.ptr)[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. - */ -void *x11_make_greeting(int endian, int protomajor, int protominor, - int auth_proto, const void *auth_data, int auth_len, - const char *peer_addr, int peer_port, - int *outlen) -{ - unsigned char *greeting; - unsigned char realauthdata[64]; - const char *authname; - const unsigned char *authdata; - int authnamelen, authnamelen_pad; - int authdatalen, authdatalen_pad; - int greeting_len; - - authname = x11_authnames[auth_proto]; - authnamelen = strlen(authname); - authnamelen_pad = (authnamelen + 3) & ~3; - - if (auth_proto == X11_MIT) { - authdata = auth_data; - authdatalen = auth_len; - } else if (auth_proto == X11_XDM && auth_len == 16) { - time_t t; - unsigned long peer_ip = 0; - - x11_parse_ip(peer_addr, &peer_ip); - - authdata = realauthdata; - authdatalen = 24; - memset(realauthdata, 0, authdatalen); - memcpy(realauthdata, auth_data, 8); - PUT_32BIT_MSB_FIRST(realauthdata+8, peer_ip); - PUT_16BIT_MSB_FIRST(realauthdata+12, peer_port); - t = time(NULL); - PUT_32BIT_MSB_FIRST(realauthdata+14, t); - - des_encrypt_xdmauth((char *)auth_data + 9, realauthdata, authdatalen); - } else { - authdata = realauthdata; - authdatalen = 0; - } - - authdatalen_pad = (authdatalen + 3) & ~3; - greeting_len = 12 + authnamelen_pad + authdatalen_pad; - - greeting = snewn(greeting_len, unsigned char); - memset(greeting, 0, greeting_len); - greeting[0] = endian; - PUT_16BIT_X11(endian, greeting+2, protomajor); - PUT_16BIT_X11(endian, greeting+4, protominor); - PUT_16BIT_X11(endian, greeting+6, authnamelen); - PUT_16BIT_X11(endian, greeting+8, authdatalen); - memcpy(greeting+12, authname, authnamelen); - memcpy(greeting+12+authnamelen_pad, authdata, authdatalen); - - smemclr(realauthdata, sizeof(realauthdata)); - - *outlen = greeting_len; - return greeting; -}