// Copyright 2005 Ben Hutchings <ben@decadentplace.org.uk>.
// See the file "COPYING" for licence details.
+#include "framebuffer.hpp"
+
#include <cassert>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
-#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <wait.h>
-#include "framebuffer.hpp"
#include "auto_fd.hpp"
+#include "auto_handle.hpp"
+#include "temp_file.hpp"
namespace
{
- int select_display_num()
+ struct addrinfo_factory
+ {
+ addrinfo * operator()() const { return NULL; }
+ };
+ struct addrinfo_closer
+ {
+ void operator()(addrinfo * addr_list) const
+ {
+ if (addr_list)
+ freeaddrinfo(addr_list);
+ }
+ };
+ typedef auto_handle<addrinfo *, addrinfo_closer, addrinfo_factory>
+ auto_addrinfo;
+
+ int select_display_num(auto_fd & tcp4_socket, auto_fd & tcp6_socket)
{
// Minimum and maximum display numbers to use. Xvnc and ssh's
// proxies start at 10, so we'll follow that convention. We
const int min_display_num = 10;
const int max_display_num = 99;
- // Note that we have to leave it to the X server to create the
- // lock file for the selected display number, which leaves a
- // race condition. We could perhaps read its error stream to
- // detect the case where another server grabs the display
- // number before it.
- char lock_file_name[20];
for (int display_num = min_display_num;
display_num <= max_display_num;
++display_num)
{
- // Check whether a lock file exists for this display. Really we
- // should also check for stale locks, but this will probably do.
+ // Check that there's no lock file for the local socket
+ // for this display. We could also check for stale locks,
+ // but this will probably do.
+ char lock_file_name[20];
std::sprintf(lock_file_name, "/tmp/.X%d-lock", display_num);
- if (access(lock_file_name, 0) == -1 && errno == ENOENT)
+ if (!(access(lock_file_name, 0) == -1 && errno == ENOENT))
+ continue;
+
+ // Attempt to create TCP socket(s) and bind them to the
+ // appropriate port number. We won't set the X server to
+ // listen on a TCP socket but this does ensure that ssh
+ // isn't using and won't use this display number. This is
+ // roughly based on the x11_create_display_inet function
+ // in OpenSSH.
+
+ auto_addrinfo addr_list;
+
+ {
+ addrinfo hints = {};
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ char port_str[5 + 1];
+ std::sprintf(port_str, "%d", 6000 + display_num);
+ addrinfo * addr_list_temp;
+ int error = getaddrinfo(NULL, port_str, &hints,
+ &addr_list_temp);
+ if (error != 0)
+ throw std::runtime_error(
+ std::string("getaddrinfo: ")
+ .append(gai_strerror(error)));
+ addr_list.reset(addr_list_temp);
+ }
+
+ const addrinfo * addr;
+
+ for (addr = addr_list.get(); addr != NULL; addr = addr->ai_next)
+ {
+ // We're only interested in TCPv4 and TCPv6.
+ if (addr->ai_family != AF_INET && addr->ai_family != AF_INET6)
+ continue;
+ auto_fd & tcp_socket =
+ (addr->ai_family == AF_INET) ? tcp4_socket : tcp6_socket;
+
+ tcp_socket.reset(socket(addr->ai_family,
+ SOCK_STREAM,
+ addr->ai_protocol));
+ if (tcp_socket.get() < 0)
+ {
+ // If the family is unsupported, no-one can bind
+ // to this address, so this is not a problem.
+ if (errno == EAFNOSUPPORT
+# ifdef EPFNOSUPPORT
+ || errno == EPFNOSUPPORT
+# endif
+ )
+ continue;
+ throw std::runtime_error(
+ std::string("socket: ").append(strerror(errno)));
+ }
+
+ // Don't let TCPv6 sockets interfere with TCPv4 sockets.
+# ifdef IPV6_V6ONLY
+ if (addr->ai_family == AF_INET6)
+ {
+ int on = 1;
+ if (setsockopt(tcp_socket.get(), IPPROTO_IPV6, IPV6_V6ONLY,
+ &on, sizeof(on)) != 0)
+ {
+ throw std::runtime_error(
+ std::string("setsockopt IPV6_V6ONLY: ")
+ .append(strerror(errno)));
+ }
+ }
+# endif
+
+ if (bind(tcp_socket.get(), addr->ai_addr, addr->ai_addrlen)
+ != 0)
+ break;
+ }
+
+ // If we reached the end of the address list, we've
+ // successfully bound to all appropriate addresses for
+ // this display number, so we can use it.
+ if (addr == NULL)
return display_num;
}
throw std::runtime_error("did not find a free X display");
}
- void get_random_bytes(unsigned char * buf, std::size_t len)
+ void get_random_bytes(unsigned char * buf, int len)
{
+ assert(len > 0);
auto_fd random_fd(open("/dev/urandom", O_RDONLY));
if (random_fd.get() == -1 || read(random_fd.get(), buf, len) != len)
throw std::runtime_error(std::strerror(errno));
}
- auto_temp_file create_temp_auth_file(int display_num)
+ std::auto_ptr<temp_file> create_temp_auth_file(int display_num)
{
- char auth_file_name[] = "/tmp/Xvfb-auth-XXXXXX";
- auto_fd auth_file_fd(mkstemp(auth_file_name));
- if (auth_file_fd.get() == -1)
- throw std::runtime_error(std::strerror(errno));
- auto_temp_file auth_file(auth_file_name);
-
- // mkstemp may use lax permissions, so fix that before writing
- // the auth data to it.
- fchmod(auth_file_fd.get(), S_IREAD|S_IWRITE);
- ftruncate(auth_file_fd.get(), 0);
+ std::auto_ptr<temp_file> auth_file(new temp_file("Xvfb-auth-"));
// An xauth entry consists of the following fields. All u16 fields
// are big-endian and unaligned. Character arrays are not null-
// u16 length of auth data (= 16)
// char[] auth data (= random bytes)
uint16_t family = htons(0x100);
- write(auth_file_fd.get(), &family, sizeof(family));
+ write(auth_file->get_fd(), &family, sizeof(family));
utsname my_uname;
uname(&my_uname);
uint16_t len = htons(strlen(my_uname.nodename));
- write(auth_file_fd.get(), &len, sizeof(len));
- write(auth_file_fd.get(),
+ write(auth_file->get_fd(), &len, sizeof(len));
+ write(auth_file->get_fd(),
my_uname.nodename, strlen(my_uname.nodename));
char display[15];
std::sprintf(display, "%d", display_num);
len = htons(strlen(display));
- write(auth_file_fd.get(), &len, sizeof(len));
- write(auth_file_fd.get(), display, strlen(display));
+ write(auth_file->get_fd(), &len, sizeof(len));
+ write(auth_file->get_fd(), display, strlen(display));
static const char auth_type[] = "MIT-MAGIC-COOKIE-1";
len = htons(sizeof(auth_type) - 1);
- write(auth_file_fd.get(), &len, sizeof(len));
- write(auth_file_fd.get(), auth_type, sizeof(auth_type) - 1);
+ write(auth_file->get_fd(), &len, sizeof(len));
+ write(auth_file->get_fd(), auth_type, sizeof(auth_type) - 1);
unsigned char auth_key[16];
get_random_bytes(auth_key, sizeof(auth_key));
len = htons(sizeof(auth_key));
- write(auth_file_fd.get(), &len, sizeof(len));
- write(auth_file_fd.get(), auth_key, sizeof(auth_key));
+ write(auth_file->get_fd(), &len, sizeof(len));
+ write(auth_file->get_fd(), auth_key, sizeof(auth_key));
return auth_file;
}
execlp("Xvfb",
"Xvfb",
"-auth", auth_file_c_str,
+ "-nolisten", "tcp",
"-screen", "0", dimensions,
+ "-terminate",
display,
NULL);
_exit(128 + errno);
}
FrameBuffer::FrameBuffer(int width, int height, int depth)
- : display_num_(select_display_num()),
+ : display_num_(select_display_num(tcp4_socket_, tcp6_socket_)),
auth_file_(create_temp_auth_file(display_num_)),
server_proc_(spawn_x_server(display_num_,
get_x_authority(),
std::string FrameBuffer::get_x_authority() const
{
- return auth_file_.get();
+ return auth_file_->get_name();
}
std::string FrameBuffer::get_x_display() const