diff --git a/ssh.h b/ssh.h index 3e61de15..bb99997e 100644 --- a/ssh.h +++ b/ssh.h @@ -1002,6 +1002,9 @@ char *platform_get_x_display(void); */ void x11_get_auth_from_authfile(struct X11Display *display, const char *authfilename); +void x11_format_auth_for_authfile( + BinarySink *bs, SockAddr *addr, int display_no, + ptrlen authproto, ptrlen authdata); int x11_identify_auth_proto(ptrlen protoname); void *x11_dehexify(ptrlen hex, int *outlen); diff --git a/unix/ux_x11.c b/unix/ux_x11.c index 63a92b58..91c95bbc 100644 --- a/unix/ux_x11.c +++ b/unix/ux_x11.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "putty.h" #include "ssh.h" @@ -38,3 +40,166 @@ void platform_get_x11_auth(struct X11Display *disp, Conf *conf) } const int platform_uses_x11_unix_by_default = TRUE; + +int platform_make_x11_server(Plug *plug, const char *progname, int mindisp, + const char *screen_number_suffix, + ptrlen authproto, ptrlen authdata, + Socket **sockets, Conf *conf) +{ + char *tmpdir; + char *authfilename = NULL; + strbuf *authfiledata = NULL; + char *unix_path = NULL; + + SockAddr *a_tcp = NULL, *a_unix = NULL; + + int authfd; + FILE *authfp; + + int displayno; + + authfiledata = strbuf_new(); + + int nsockets = 0; + + /* + * Look for a free TCP port to run our server on. + */ + for (displayno = mindisp;; displayno++) { + const char *err; + int tcp_port = displayno + 6000; + int addrtype = ADDRTYPE_IPV4; + + sockets[nsockets] = new_listener( + NULL, tcp_port, plug, FALSE, conf, addrtype); + + err = sk_socket_error(sockets[nsockets]); + if (!err) { + char *hostname = get_hostname(); + if (hostname) { + char *canonicalname = NULL; + a_tcp = name_lookup(hostname, tcp_port, &canonicalname, + conf, addrtype, NULL, ""); + sfree(canonicalname); + } + sfree(hostname); + nsockets++; + break; /* success! */ + } else { + sk_close(sockets[nsockets]); + } + + if (!strcmp(err, strerror(EADDRINUSE))) /* yuck! */ + goto out; + } + + if (a_tcp) { + x11_format_auth_for_authfile( + BinarySink_UPCAST(authfiledata), + a_tcp, displayno, authproto, authdata); + } + + /* + * Try to establish the Unix-domain analogue. That may or may not + * work - file permissions in /tmp may prevent it, for example - + * but it's worth a try, and we don't consider it a fatal error if + * it doesn't work. + */ + unix_path = dupprintf("/tmp/.X11-unix/X%d", displayno); + a_unix = unix_sock_addr(unix_path); + + sockets[nsockets] = new_unix_listener(a_unix, plug); + if (!sk_socket_error(sockets[nsockets])) { + x11_format_auth_for_authfile( + BinarySink_UPCAST(authfiledata), + a_unix, displayno, authproto, authdata); + nsockets++; + } else { + sk_close(sockets[nsockets]); + sfree(unix_path); + unix_path = NULL; + } + + /* + * Decide where the authority data will be written. + */ + + tmpdir = getenv("TMPDIR"); + if (!tmpdir || !*tmpdir) + tmpdir = "/tmp"; + + authfilename = dupcat(tmpdir, "/", progname, "-Xauthority-XXXXXX"); + + { + int oldumask = umask(077); + authfd = mkstemp(authfilename); + umask(oldumask); + } + if (authfd < 0) { + while (nsockets-- > 0) + sk_close(sockets[nsockets]); + goto out; + } + + /* + * Spawn a subprocess which will try to reliably delete our + * auth file when we terminate, in case we die unexpectedly. + */ + { + int cleanup_pipe[2]; + pid_t pid; + + /* Don't worry if pipe or fork fails; it's not _that_ critical. */ + if (!pipe(cleanup_pipe)) { + if ((pid = fork()) == 0) { + int buf[1024]; + /* + * Our parent process holds the writing end of + * this pipe, and writes nothing to it. Hence, + * we expect read() to return EOF as soon as + * that process terminates. + */ + setpgid(0, 0); + close(cleanup_pipe[1]); + close(authfd); + while (read(cleanup_pipe[0], buf, sizeof(buf)) > 0); + unlink(authfilename); + if (unix_path) + unlink(unix_path); + _exit(0); + } else if (pid < 0) { + close(cleanup_pipe[0]); + close(cleanup_pipe[1]); + } else { + close(cleanup_pipe[0]); + cloexec(cleanup_pipe[1]); + } + } + } + + authfp = fdopen(authfd, "wb"); + fwrite(authfiledata->u, 1, authfiledata->len, authfp); + fclose(authfp); + + { + char *display = dupprintf(":%d%s", displayno, screen_number_suffix); + conf_set_str_str(conf, CONF_environmt, "DISPLAY", display); + sfree(display); + } + conf_set_str_str(conf, CONF_environmt, "XAUTHORITY", authfilename); + + /* + * FIXME: return at least the DISPLAY and XAUTHORITY env settings, + * and perhaps also the display number + */ + + out: + if (a_tcp) + sk_addr_free(a_tcp); + if (a_unix) + sk_addr_free(a_unix); + sfree(authfilename); + strbuf_free(authfiledata); + sfree(unix_path); + return nsockets; +} diff --git a/x11fwd.c b/x11fwd.c index 8e306f42..7feb2a63 100644 --- a/x11fwd.c +++ b/x11fwd.c @@ -449,6 +449,15 @@ ptrlen BinarySource_get_string_xauth(BinarySource *src) #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_data(bs, pl.ptr, pl.len); +} +#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) { @@ -631,6 +640,39 @@ void x11_get_auth_from_authfile(struct X11Display *disp, 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 { + assert(FALSE && "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, int type, SockAddr *addr, int port, const char *error_msg, int error_code) {