diff options
Diffstat (limited to 'utils/x11authfile.c')
-rw-r--r-- | utils/x11authfile.c | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/utils/x11authfile.c b/utils/x11authfile.c new file mode 100644 index 00000000..e2358a9d --- /dev/null +++ b/utils/x11authfile.c @@ -0,0 +1,244 @@ +/* + * Functions to handle .Xauthority files. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> + +#include "putty.h" +#include "ssh.h" + +static 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)) + +static 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); +} |