diff options
Diffstat (limited to 'daemon/gdm-xdmcp-display-factory.c')
-rw-r--r-- | daemon/gdm-xdmcp-display-factory.c | 3478 |
1 files changed, 3478 insertions, 0 deletions
diff --git a/daemon/gdm-xdmcp-display-factory.c b/daemon/gdm-xdmcp-display-factory.c new file mode 100644 index 0000000..220ee3a --- /dev/null +++ b/daemon/gdm-xdmcp-display-factory.c @@ -0,0 +1,3478 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- + * + * Copyright (C) 1998, 1999, 2000 Martin K. Petersen <mkp@mkp.net> + * Copyright (C) 2007 William Jon McCann <mccann@jhu.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/utsname.h> + +#include <sys/socket.h> +#include <netdb.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <netinet/in.h> +#ifdef HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif +#include <sys/ioctl.h> + +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <glib/gstdio.h> +#include <glib-object.h> + +#include <X11/Xlib.h> +#include <X11/Xmd.h> +#include <X11/Xdmcp.h> + +#include "gdm-common.h" +#include "gdm-xdmcp-chooser-display.h" +#include "gdm-display-factory.h" +#include "gdm-launch-environment.h" +#include "gdm-xdmcp-display-factory.h" +#include "gdm-display-store.h" +#include "gdm-settings-direct.h" +#include "gdm-settings-keys.h" + +/* + * On Sun, we need to define allow_severity and deny_severity to link + * against libwrap. + */ +#ifdef __sun +#include <syslog.h> +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#define DEFAULT_PORT 177 +#define DEFAULT_USE_MULTICAST FALSE +#define DEFAULT_MULTICAST_ADDRESS "ff02::1" +#define DEFAULT_HONOR_INDIRECT TRUE +#define DEFAULT_MAX_DISPLAYS_PER_HOST 1 +#define DEFAULT_MAX_DISPLAYS 16 +#define DEFAULT_MAX_PENDING_DISPLAYS 4 +#define DEFAULT_MAX_WAIT 30 +#define DEFAULT_MAX_WAIT_INDIRECT 30 +#define DEFAULT_WILLING_SCRIPT GDMCONFDIR "/Xwilling" + +#define GDM_MAX_FORWARD_QUERIES 10 +#define GDM_FORWARD_QUERY_TIMEOUT 30 +#define MANAGED_FORWARD_INTERVAL 1500 /* 1.5 seconds */ + +/* some extra XDMCP opcodes that xdm will happily ignore since they'll be + * the wrong XDMCP version anyway */ +#define GDM_XDMCP_PROTOCOL_VERSION 1001 +enum { + GDM_XDMCP_FIRST_OPCODE = 1000, /*just a marker, not an opcode */ + + GDM_XDMCP_MANAGED_FORWARD = 1000, + /* manager (master) -> manager + * A packet with MANAGED_FORWARD is sent to the + * manager that sent the forward query from the manager to + * which forward query was sent. It indicates that the forward + * was fully processed and that the client now has either + * a managed session, or has been sent denial, refuse or failed. + * (if the denial gets lost then client gets dumped into the + * chooser again). This should be resent a few times + * until some (short) timeout or until GOT_MANAGED_FORWARD + * is sent. GDM sends at most 3 packates with 1.5 seconds + * between each. + * + * Argument is ARRAY8 with the address of the originating host */ + GDM_XDMCP_GOT_MANAGED_FORWARD, + /* manager -> manager (master) + * A single packet with GOT_MANAGED_FORWARD is sent to indicate + * that we did receive the MANAGED_FORWARD packet. The argument + * must match the MANAGED_FORWARD one or it will just be ignored. + * + * Argument is ARRAY8 with the address of the originating host */ + GDM_XDMCP_LAST_OPCODE /*just a marker, not an opcode */ +}; + +/* + * We don't support XDM-AUTHENTICATION-1 and XDM-AUTHORIZATION-1. + * + * The latter would be quite useful to avoid sending unencrypted + * cookies over the wire. Unfortunately it isn't supported without + * XDM-AUTHENTICATION-1 which requires a key database with private + * keys from all X terminals on your LAN. Fun, fun, fun. + * + * Furthermore user passwords go over the wire in cleartext anyway, + * so protecting cookies is not that important. + */ + +typedef struct _XdmAuth { + ARRAY8 authentication; + ARRAY8 authorization; +} XdmAuthRec, *XdmAuthPtr; + +static XdmAuthRec serv_authlist = { + { (CARD16) 0, (CARD8 *) 0 }, + { (CARD16) 0, (CARD8 *) 0 } +}; + +/* NOTE: Timeout and max are hardcoded */ +typedef struct _ForwardQuery { + time_t acctime; + GdmAddress *dsp_address; + GdmAddress *from_address; +} ForwardQuery; + +typedef struct _IndirectClient { + int id; + GdmAddress *dsp_address; + GdmAddress *chosen_address; + time_t acctime; +} IndirectClient; + +typedef struct { + int times; + guint handler; + GdmAddress *manager; + GdmAddress *origin; + GdmXdmcpDisplayFactory *xdmcp_display_factory; +} ManagedForward; + +struct _GdmXdmcpDisplayFactory +{ + GdmDisplayFactory parent; + + GSList *forward_queries; + GSList *managed_forwards; + GSList *indirect_clients; + + int socket_fd; + gint32 session_serial; + guint socket_watch_id; + XdmcpBuffer buf; + + guint num_sessions; + guint num_pending_sessions; + + char *sysid; + char *hostname; + ARRAY8 servhost; + + /* configuration */ + guint port; + gboolean use_multicast; + char *multicast_address; + gboolean honor_indirect; + char *willing_script; + guint max_displays_per_host; + guint max_displays; + guint max_pending_displays; + guint max_wait; + guint max_wait_indirect; +}; + +enum { + PROP_0, + PROP_PORT, + PROP_USE_MULTICAST, + PROP_MULTICAST_ADDRESS, + PROP_HONOR_INDIRECT, + PROP_WILLING_SCRIPT, + PROP_MAX_DISPLAYS_PER_HOST, + PROP_MAX_DISPLAYS, + PROP_MAX_PENDING_DISPLAYS, + PROP_MAX_WAIT, + PROP_MAX_WAIT_INDIRECT, +}; + +static void gdm_xdmcp_display_factory_class_init (GdmXdmcpDisplayFactoryClass *klass); +static void gdm_xdmcp_display_factory_init (GdmXdmcpDisplayFactory *manager); +static void gdm_xdmcp_display_factory_finalize (GObject *object); +static void gdm_xdmcp_send_alive (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD16 dspnum, + CARD32 sessid); +static gpointer xdmcp_display_factory_object = NULL; + +G_DEFINE_TYPE (GdmXdmcpDisplayFactory, gdm_xdmcp_display_factory, GDM_TYPE_DISPLAY_FACTORY) + +/* Theory of operation: + * + * Process idles waiting for UDP packets on port 177. + * Incoming packets are decoded and checked against tcp_wrapper. + * + * A typical session looks like this: + * + * Display sends Query/BroadcastQuery to Manager. + * + * Manager selects an appropriate authentication scheme from the + * display's list of supported ones and sends Willing/Unwilling. + * + * Assuming the display accepts the auth. scheme it sends back a + * Request. + * + * If the manager accepts to service the display (i.e. loadavg is low) + * it sends back an Accept containing a unique SessionID. The + * SessionID is stored in an accept queue by the Manager. Should the + * manager refuse to start a session a Decline is sent to the display. + * + * The display returns a Manage request containing the supplied + * SessionID. The manager will then start a session on the display. In + * case the SessionID is not on the accept queue the manager returns + * Refuse. If the manager fails to open the display for connections + * Failed is returned. + * + * During the session the display periodically sends KeepAlive packets + * to the manager. The manager responds with Alive. + * + * Similarly the manager xpings the display once in a while and shuts + * down the connection on failure. + * + */ + +GQuark +gdm_xdmcp_display_factory_error_quark (void) +{ + static GQuark ret = 0; + if (ret == 0) { + ret = g_quark_from_static_string ("gdm_xdmcp_display_factory_error"); + } + + return ret; +} + +static gint32 +get_next_session_serial (GdmXdmcpDisplayFactory *factory) +{ + gint32 serial; + + again: + if (factory->session_serial != G_MAXINT32) { + serial = factory->session_serial++; + } else { + serial = g_random_int (); + } + + if (serial == 0) { + goto again; + } + + return serial; +} + +/* for debugging */ +static const char * +ai_family_str (struct addrinfo *ai) +{ + const char *str; + switch (ai->ai_family) { + case AF_INET: + str = "inet"; + break; + case AF_INET6: + str = "inet6"; + break; + case AF_UNIX: + str = "unix"; + break; + case AF_UNSPEC: + str = "unspecified"; + break; + default: + str = "unknown"; + break; + } + return str; +} + +/* for debugging */ +static const char * +ai_type_str (struct addrinfo *ai) +{ + const char *str; + switch (ai->ai_socktype) { + case SOCK_STREAM: + str = "stream"; + break; + case SOCK_DGRAM: + str = "datagram"; + break; + case SOCK_SEQPACKET: + str = "seqpacket"; + break; + case SOCK_RAW: + str = "raw"; + break; + default: + str = "unknown"; + break; + } + return str; +} + +/* for debugging */ +static const char * +ai_protocol_str (struct addrinfo *ai) +{ + const char *str; + switch (ai->ai_protocol) { + case 0: + str = "default"; + break; + case IPPROTO_TCP: + str = "TCP"; + break; + case IPPROTO_UDP: + str = "UDP"; + break; + case IPPROTO_RAW: + str = "raw"; + break; + default: + str = "unknown"; + break; + } + + return str; +} + +/* for debugging */ +static char * +ai_flags_str (struct addrinfo *ai) +{ + GString *str; + + str = g_string_new (""); + if (ai->ai_flags == 0) { + g_string_append (str, "none"); + } else { + if (ai->ai_flags & AI_PASSIVE) { + g_string_append (str, "passive "); + } + if (ai->ai_flags & AI_CANONNAME) { + g_string_append (str, "canon "); + } + if (ai->ai_flags & AI_NUMERICHOST) { + g_string_append (str, "numhost "); + } + if (ai->ai_flags & AI_NUMERICSERV) { + g_string_append (str, "numserv "); + } +#ifdef AI_V4MAPPEP + if (ai->ai_flags & AI_V4MAPPED) { + g_string_append (str, "v4mapped "); + } +#endif +#ifdef AI_ALL + if (ai->ai_flags & AI_ALL) { + g_string_append (str, "all "); + } +#endif + } + return g_string_free (str, FALSE); +} + +/* for debugging */ +static void +debug_addrinfo (struct addrinfo *ai) +{ + char *str; + str = ai_flags_str (ai); + g_debug ("GdmXdmcpDisplayFactory: addrinfo family=%s type=%s proto=%s flags=%s", + ai_family_str (ai), + ai_type_str (ai), + ai_protocol_str (ai), + str); + g_free (str); +} + +static int +create_socket (struct addrinfo *ai) +{ + int sock; + + sock = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + g_warning ("socket: %s", g_strerror (errno)); + return sock; + } + +#if defined(ENABLE_IPV6) && defined(IPV6_V6ONLY) + if (ai->ai_family == AF_INET6) { + int zero = 0; + if (setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) < 0) + g_warning("setsockopt(IPV6_V6ONLY): %s", g_strerror(errno)); + } +#endif + + if (bind (sock, ai->ai_addr, ai->ai_addrlen) < 0) { + g_warning ("bind: %s", g_strerror (errno)); + close (sock); + return -1; + } + + return sock; +} + +static int +do_bind (guint port, + int family, + struct sockaddr_storage * hostaddr) +{ + struct addrinfo hints; + struct addrinfo *ai_list; + struct addrinfo *ai; + char strport[NI_MAXSERV]; + int gaierr; + int sock; + + sock = -1; + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_PASSIVE; + + snprintf (strport, sizeof (strport), "%u", port); + + ai_list = NULL; + if ((gaierr = getaddrinfo (NULL, strport, &hints, &ai_list)) != 0) { + g_error ("Unable to connect to socket: %s", gai_strerror (gaierr)); + } + + /* should only be one but.. */ + for (ai = ai_list; ai != NULL; ai = ai->ai_next) { + if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6) { + continue; + } + + debug_addrinfo (ai); + + if (sock < 0) { + char *host; + char *serv; + GdmAddress *addr; + + addr = gdm_address_new_from_sockaddr (ai->ai_addr, ai->ai_addrlen); + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (addr, &host, &serv); + g_debug ("GdmXdmcpDisplayFactory: Attempting to bind to host %s port %s", + host ? host : "(null)", serv ? serv : "(null)"); + g_free (host); + g_free (serv); + gdm_address_free (addr); + + sock = create_socket (ai); + if (sock >= 0) { + if (hostaddr != NULL) { + memcpy (hostaddr, ai->ai_addr, ai->ai_addrlen); + } + } + } + } + + freeaddrinfo (ai_list); + + return sock; +} + +static void +setup_multicast (GdmXdmcpDisplayFactory *factory) +{ +#ifdef ENABLE_IPV6 + /* Checking and Setting Multicast options */ + { + /* + * socktemp is a temporary socket for getting info about + * available interfaces + */ + int socktemp; + int i; + int num; + char *buf; + struct ipv6_mreq mreq; + + /* For interfaces' list */ + struct ifconf ifc; + struct ifreq *ifr; + + socktemp = socket (AF_INET, SOCK_DGRAM, 0); +#ifdef SIOCGIFNUM + if (ioctl (socktemp, SIOCGIFNUM, &num) < 0) { + num = 64; + } +#else + num = 64; +#endif /* SIOCGIFNUM */ + ifc.ifc_len = sizeof (struct ifreq) * num; + ifc.ifc_buf = buf = malloc (ifc.ifc_len); + + if (ioctl (socktemp, SIOCGIFCONF, &ifc) >= 0) { + ifr = ifc.ifc_req; + num = ifc.ifc_len / sizeof (struct ifreq); /* No of interfaces */ + + /* Joining multicast group with all interfaces */ + for (i = 0 ; i < num ; i++) { + struct ifreq ifreq; + int ifindex; + + memset (&ifreq, 0, sizeof (ifreq)); + strncpy (ifreq.ifr_name, ifr[i].ifr_name, sizeof (ifreq.ifr_name)); + /* paranoia */ + ifreq.ifr_name[sizeof (ifreq.ifr_name) - 1] = '\0'; + + if (ioctl (socktemp, SIOCGIFFLAGS, &ifreq) < 0) { + g_debug ("GdmXdmcpDisplayFactory: Could not get SIOCGIFFLAGS for %s", + ifr[i].ifr_name); + } + + ifindex = if_nametoindex (ifr[i].ifr_name); + + if ((!(ifreq.ifr_flags & IFF_UP) || + (ifreq.ifr_flags & IFF_LOOPBACK)) || + ((ifindex == 0 ) && (errno == ENXIO))) { + /* Not a valid interface or loopback interface*/ + continue; + } + + mreq.ipv6mr_interface = ifindex; + inet_pton (AF_INET6, + factory->multicast_address, + &mreq.ipv6mr_multiaddr); + + setsockopt (factory->socket_fd, + IPPROTO_IPV6, + IPV6_JOIN_GROUP, + &mreq, + sizeof (mreq)); + } + } + g_free (buf); + close (socktemp); + } +#endif /* ENABLE_IPV6 */ +} + +static void +fd_set_close_on_exec (int fd) +{ + int flags; + + flags = fcntl (fd, F_GETFD, 0); + if (flags < 0) { + return; + } + + flags |= FD_CLOEXEC; + + fcntl (fd, F_SETFD, flags); +} + +static gboolean +open_port (GdmXdmcpDisplayFactory *factory) +{ + struct sockaddr_storage serv_sa = { 0 }; + + g_debug ("GdmXdmcpDisplayFactory: Start up on host %s, port %d", + factory->hostname ? factory->hostname : "(null)", + factory->port); + + /* Open socket for communications */ +#ifdef ENABLE_IPV6 + factory->socket_fd = do_bind (factory->port, AF_INET6, &serv_sa); + if (factory->socket_fd < 0) +#endif + factory->socket_fd = do_bind (factory->port, AF_INET, &serv_sa); + + if G_UNLIKELY (factory->socket_fd < 0) { + g_warning (_("Could not create socket!")); + return FALSE; + } + + fd_set_close_on_exec (factory->socket_fd); + + if (factory->use_multicast) { + setup_multicast (factory); + } + + return TRUE; +} + +#ifdef HAVE_TCPWRAPPERS + + /* + * Avoids a warning, my tcpd.h file doesn't include this prototype, even + * though the library does include the function and the manpage mentions it + */ + extern int hosts_ctl (char *daemon, + char *client_name, + char *client_addr, + char *client_user); +#endif + +static gboolean +gdm_xdmcp_host_allow (GdmAddress *address) +{ +#ifdef HAVE_TCPWRAPPERS + char *client; + char *host; + gboolean ret; + + host = NULL; + client = NULL; + + /* Find client hostname */ + gdm_address_get_hostname (address, &client); + gdm_address_get_numeric_info (address, &host, NULL); + + /* Check with tcp_wrappers if client is allowed to access */ + ret = hosts_ctl ("gdm", client, host, ""); + + g_free (host); + g_free (client); + + return ret; +#else /* HAVE_TCPWRAPPERS */ + return (TRUE); +#endif /* HAVE_TCPWRAPPERS */ +} + +typedef struct { + GdmAddress *address; + int count; +} CountDisplayData; + +static void +count_displays_from_host (const char *id, + GdmDisplay *display, + CountDisplayData *data) +{ + GdmAddress *address; + + if (GDM_IS_XDMCP_DISPLAY (display)) { + address = gdm_xdmcp_display_get_remote_address (GDM_XDMCP_DISPLAY (display)); + + if (gdm_address_equal (address, data->address)) { + data->count++; + } + } +} + +static int +gdm_xdmcp_num_displays_from_host (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + CountDisplayData data; + GdmDisplayStore *store; + + data.count = 0; + data.address = address; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_foreach (store, + (GdmDisplayStoreFunc)count_displays_from_host, + &data); + + return data.count; +} + +typedef struct { + GdmAddress *address; + int display_num; +} LookupHostData; + +static gboolean +lookup_by_host (const char *id, + GdmDisplay *display, + LookupHostData *data) +{ + GdmAddress *this_address; + int disp_num; + + if (! GDM_IS_XDMCP_DISPLAY (display)) { + return FALSE; + } + + this_address = gdm_xdmcp_display_get_remote_address (GDM_XDMCP_DISPLAY (display)); + gdm_display_get_x11_display_number (display, &disp_num, NULL); + + if (gdm_address_equal (this_address, data->address) + && disp_num == data->display_num) { + return TRUE; + } + + return FALSE; +} + +static GdmDisplay * +gdm_xdmcp_display_lookup_by_host (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int display_num) +{ + GdmDisplay *display; + LookupHostData *data; + GdmDisplayStore *store; + + data = g_new0 (LookupHostData, 1); + data->address = address; + data->display_num = display_num; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + display = gdm_display_store_find (store, + (GdmDisplayStoreFunc)lookup_by_host, + data); + g_free (data); + + return display; +} + +static char * +get_willing_output (GdmXdmcpDisplayFactory *factory) +{ + char *output; + char **argv; + FILE *fd; + char buf[256]; + + output = NULL; + buf[0] = '\0'; + + if (factory->willing_script == NULL) { + goto out; + } + + argv = NULL; + if (! g_shell_parse_argv (factory->willing_script, NULL, &argv, NULL)) { + goto out; + } + + if (argv == NULL || + argv[0] == NULL || + g_access (argv[0], X_OK) != 0) { + goto out; + } + + fd = popen (factory->willing_script, "r"); + if (fd == NULL) { + goto out; + } + + if (fgets (buf, sizeof (buf), fd) == NULL) { + pclose (fd); + goto out; + } + + pclose (fd); + + output = g_strdup (buf); + + out: + return output; +} + +static void +gdm_xdmcp_send_willing (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + ARRAY8 status; + XdmcpHeader header; + static char *last_status = NULL; + static time_t last_willing = 0; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending WILLING to %s", + host ? host : "(null)"); + g_free (host); + + if (last_willing == 0 || time (NULL) - 3 > last_willing) { + char *s; + + g_free (last_status); + + s = get_willing_output (factory); + if (s != NULL) { + last_status = s; + } else { + last_status = g_strdup (factory->sysid); + } + } + + if (! gdm_address_is_local (address) && + gdm_xdmcp_num_displays_from_host (factory, address) >= factory->max_displays_per_host) { + /* + * Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii + */ + status.data = (CARD8 *) g_strdup_printf ("%s (Server is busy)", + last_status); + } else { + status.data = (CARD8 *) g_strdup (last_status); + } + + status.length = strlen ((char *) status.data); + + header.opcode = (CARD16) WILLING; + header.length = 6 + serv_authlist.authentication.length; + header.length += factory->servhost.length + status.length; + header.version = XDM_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + /* Hardcoded authentication */ + XdmcpWriteARRAY8 (&factory->buf, &serv_authlist.authentication); + XdmcpWriteARRAY8 (&factory->buf, &factory->servhost); + XdmcpWriteARRAY8 (&factory->buf, &status); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + g_free (status.data); +} + +static void +gdm_xdmcp_send_unwilling (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int type) +{ + ARRAY8 status; + XdmcpHeader header; + static time_t last_time = 0; + char *host; + + /* only send at most one packet per second, + no harm done if we don't send it at all */ + if (last_time + 1 >= time (NULL)) { + return; + } + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending UNWILLING to %s", + host ? host : "(null)"); + g_warning ("Denied XDMCP query from host %s", + host ? host : "(null)"); + g_free (host); + + /* + * Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii + */ + status.data = (CARD8 *) "Display not authorized to connect"; + status.length = strlen ((char *) status.data); + + header.opcode = (CARD16) UNWILLING; + header.length = 4 + factory->servhost.length + status.length; + header.version = XDM_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + XdmcpWriteARRAY8 (&factory->buf, &factory->servhost); + XdmcpWriteARRAY8 (&factory->buf, &status); + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + last_time = time (NULL); +} + +#define SIN(__s) ((struct sockaddr_in *) __s) +#define SIN6(__s) ((struct sockaddr_in6 *) __s) + +static void +set_port_for_request (GdmAddress *address, + ARRAY8 *port) +{ + struct sockaddr_storage *ss; + + ss = gdm_address_peek_sockaddr_storage (address); + + /* we depend on this being 2 elsewhere as well */ + port->length = 2; + + switch (ss->ss_family) { + case AF_INET: + port->data = (CARD8 *)g_memdup (&(SIN (ss)->sin_port), port->length); + break; + case AF_INET6: + port->data = (CARD8 *)g_memdup (&(SIN6 (ss)->sin6_port), port->length); + break; + default: + port->data = NULL; + break; + } +} + +static void +set_address_for_request (GdmAddress *address, + ARRAY8 *addr) +{ + struct sockaddr_storage *ss; + + ss = gdm_address_peek_sockaddr_storage (address); + + switch (ss->ss_family) { + case AF_INET: + addr->length = sizeof (struct in_addr); + addr->data = g_memdup (&SIN (ss)->sin_addr, addr->length); + break; + case AF_INET6: + addr->length = sizeof (struct in6_addr); + addr->data = g_memdup (&SIN6 (ss)->sin6_addr, addr->length); + break; + default: + addr->length = 0; + addr->data = NULL; + break; + } + +} + +static void +gdm_xdmcp_send_forward_query (GdmXdmcpDisplayFactory *factory, + IndirectClient *ic, + GdmAddress *address, + GdmAddress *display_address, + ARRAYofARRAY8Ptr authlist) +{ + XdmcpHeader header; + int i; + ARRAY8 addr; + ARRAY8 port; + char *host; + char *serv; + + g_assert (ic != NULL); + g_assert (ic->chosen_address != NULL); + + host = NULL; + gdm_address_get_numeric_info (ic->chosen_address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending forward query to %s", + host ? host : "(null)"); + g_free (host); + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (display_address, &host, &serv); + g_debug ("GdmXdmcpDisplayFactory: Query contains %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + g_free (host); + g_free (serv); + + set_port_for_request (address, &port); + set_address_for_request (display_address, &addr); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) FORWARD_QUERY; + header.length = 0; + header.length += 2 + addr.length; + header.length += 2 + port.length; + header.length += 1; + for (i = 0; i < authlist->length; i++) { + header.length += 2 + authlist->data[i].length; + } + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteARRAY8 (&factory->buf, &addr); + XdmcpWriteARRAY8 (&factory->buf, &port); + XdmcpWriteARRAYofARRAY8 (&factory->buf, authlist); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (ic->chosen_address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (ic->chosen_address))); + + g_free (port.data); + g_free (addr.data); +} + +static void +handle_any_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + ARRAYofARRAY8Ptr authentication_names, + int type) +{ + gdm_xdmcp_send_willing (factory, address); +} + +static void +handle_direct_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len, + int type) +{ + ARRAYofARRAY8 clnt_authlist; + int expected_len; + int i; + int res; + + res = XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authlist); + if G_UNLIKELY (! res) { + g_warning ("Could not extract authlist from packet"); + return; + } + + expected_len = 1; + + for (i = 0 ; i < clnt_authlist.length ; i++) { + expected_len += 2 + clnt_authlist.data[i].length; + } + + if (len == expected_len) { + handle_any_query (factory, address, &clnt_authlist, type); + } else { + g_warning ("Error in checksum"); + } + + XdmcpDisposeARRAYofARRAY8 (&clnt_authlist); +} + +static void +gdm_xdmcp_handle_broadcast_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + if (gdm_xdmcp_host_allow (address)) { + handle_direct_query (factory, address, len, BROADCAST_QUERY); + } else { + /* just ignore it */ + } +} + +static void +gdm_xdmcp_handle_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + if (gdm_xdmcp_host_allow (address)) { + handle_direct_query (factory, address, len, QUERY); + } else { + gdm_xdmcp_send_unwilling (factory, address, QUERY); + } +} + +static IndirectClient * +indirect_client_create (GdmXdmcpDisplayFactory *factory, + GdmAddress *dsp_address) +{ + IndirectClient *ic; + + ic = g_new0 (IndirectClient, 1); + ic->dsp_address = gdm_address_copy (dsp_address); + + factory->indirect_clients = g_slist_prepend (factory->indirect_clients, ic); + + return ic; +} + +static void +indirect_client_destroy (GdmXdmcpDisplayFactory *factory, + IndirectClient *ic) +{ + if (ic == NULL) { + return; + } + + factory->indirect_clients = g_slist_remove (factory->indirect_clients, ic); + + ic->acctime = 0; + + { + char *host; + + host = NULL; + gdm_address_get_numeric_info (ic->dsp_address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Disposing IndirectClient for %s", + host ? host : "(null)"); + g_free (host); + } + + g_free (ic->dsp_address); + ic->dsp_address = NULL; + g_free (ic->chosen_address); + ic->chosen_address = NULL; + + g_free (ic); +} + +static IndirectClient * +indirect_client_lookup_by_chosen (GdmXdmcpDisplayFactory *factory, + GdmAddress *chosen_address, + GdmAddress *origin_address) +{ + GSList *li; + char *host; + IndirectClient *ret; + + g_assert (chosen_address != NULL); + g_assert (origin_address != NULL); + + ret = NULL; + + for (li = factory->indirect_clients; li != NULL; li = li->next) { + IndirectClient *ic = li->data; + + if (ic != NULL + && ic->chosen_address != NULL + && gdm_address_equal (ic->chosen_address, chosen_address)) { + if (gdm_address_equal (ic->dsp_address, origin_address)) { + ret = ic; + goto out; + } else if (gdm_address_is_loopback (ic->dsp_address) + && gdm_address_is_local (origin_address)) { + ret = ic; + goto out; + } + } + } + + gdm_address_get_numeric_info (chosen_address, &host, NULL); + + g_debug ("GdmXdmcpDisplayFactory: Chosen %s host not found", + host ? host : "(null)"); + g_free (host); + out: + return ret; +} + +/* lookup by origin */ +static IndirectClient * +indirect_client_lookup (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + GSList *li; + GSList *qlist; + IndirectClient *ret; + time_t curtime; + + g_assert (address != NULL); + + curtime = time (NULL); + ret = NULL; + + qlist = g_slist_copy (factory->indirect_clients); + + for (li = qlist; li != NULL; li = li->next) { + IndirectClient *ic; + char *host; + char *serv; + + ic = (IndirectClient *) li->data; + + if (ic == NULL) { + continue; + } + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (ic->dsp_address, &host, &serv); + + g_debug ("GdmXdmcpDisplayFactory: comparing %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + if (gdm_address_equal (ic->dsp_address, address)) { + ret = ic; + g_free (host); + g_free (serv); + break; + } + + if (ic->acctime > 0 && curtime > ic->acctime + factory->max_wait_indirect) { + g_debug ("GdmXdmcpDisplayFactory: Disposing stale forward query from %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + + indirect_client_destroy (factory, ic); + } + + g_free (host); + g_free (serv); + } + + g_slist_free (qlist); + + if (ret == NULL) { + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Host %s not found", + host ? host : "(null)"); + g_free (host); + } + + return ret; +} + +static void +gdm_xdmcp_handle_indirect_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + ARRAYofARRAY8 clnt_authlist; + int expected_len; + int i; + int res; + IndirectClient *ic; + + if (! gdm_xdmcp_host_allow (address)) { + /* ignore the request */ + return; + } + + if (! factory->honor_indirect) { + /* ignore it */ + return; + } + + if (factory->num_sessions > factory->max_displays || + (!gdm_address_is_local (address) && + gdm_xdmcp_num_displays_from_host (factory, address) > factory->max_displays_per_host)) { + g_debug ("GdmXdmcpDisplayFactory: reached maximum number of clients - ignoring indirect query"); + return; + } + + res = XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authlist); + if G_UNLIKELY (! res) { + g_warning ("Could not extract authlist from packet"); + return; + } + + expected_len = 1; + + for (i = 0 ; i < clnt_authlist.length ; i++) { + expected_len += 2 + clnt_authlist.data[i].length; + } + + /* Try to look up the display in + * the pending list. If found send a FORWARD_QUERY to the + * chosen manager. Otherwise alloc a new indirect display. */ + + if (len != expected_len) { + g_warning ("Error in checksum"); + goto out; + } + + + ic = indirect_client_lookup (factory, address); + + if (ic != NULL && ic->chosen_address != NULL) { + /* if user chose us, then just send willing */ + if (gdm_address_is_local (ic->chosen_address)) { + g_debug ("GdmXdmcpDisplayFactory: the chosen address is local - dropping indirect"); + + /* get rid of indirect, so that we don't get + * the chooser */ + indirect_client_destroy (factory, ic); + gdm_xdmcp_send_willing (factory, address); + } else if (gdm_address_is_loopback (address)) { + /* woohoo! fun, I have no clue how to get + * the correct ip, SO I just send forward + * queries with all the different IPs */ + const GList *list = gdm_address_peek_local_list (); + + g_debug ("GdmXdmcpDisplayFactory: the chosen address is a loopback"); + + while (list != NULL) { + GdmAddress *saddr = list->data; + + if (! gdm_address_is_loopback (saddr)) { + /* forward query to * chosen host */ + gdm_xdmcp_send_forward_query (factory, + ic, + address, + saddr, + &clnt_authlist); + } + + list = list->next; + } + } else { + /* or send forward query to chosen host */ + gdm_xdmcp_send_forward_query (factory, + ic, + address, + address, + &clnt_authlist); + } + } else if (ic == NULL) { + ic = indirect_client_create (factory, address); + if (ic != NULL) { + gdm_xdmcp_send_willing (factory, address); + } + } else { + gdm_xdmcp_send_willing (factory, address); + } + +out: + XdmcpDisposeARRAYofARRAY8 (&clnt_authlist); +} + +static void +forward_query_destroy (GdmXdmcpDisplayFactory *factory, + ForwardQuery *q) +{ + if (q == NULL) { + return; + } + + factory->forward_queries = g_slist_remove (factory->forward_queries, q); + + q->acctime = 0; + + { + char *host; + + host = NULL; + gdm_address_get_numeric_info (q->dsp_address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Disposing %s", + host ? host : "(null)"); + g_free (host); + } + + g_free (q->dsp_address); + q->dsp_address = NULL; + g_free (q->from_address); + q->from_address = NULL; + + g_free (q); +} + +static gboolean +remove_oldest_forward (GdmXdmcpDisplayFactory *factory) +{ + GSList *li; + ForwardQuery *oldest = NULL; + + for (li = factory->forward_queries; li != NULL; li = li->next) { + ForwardQuery *query = li->data; + + if (oldest == NULL || query->acctime < oldest->acctime) { + oldest = query; + } + } + + if (oldest != NULL) { + forward_query_destroy (factory, oldest); + return TRUE; + } else { + return FALSE; + } +} + +static ForwardQuery * +forward_query_create (GdmXdmcpDisplayFactory *factory, + GdmAddress *mgr_address, + GdmAddress *dsp_address) +{ + ForwardQuery *q; + int count; + + count = g_slist_length (factory->forward_queries); + + while (count > GDM_MAX_FORWARD_QUERIES && remove_oldest_forward (factory)) { + count--; + } + + q = g_new0 (ForwardQuery, 1); + q->dsp_address = gdm_address_copy (dsp_address); + q->from_address = gdm_address_copy (mgr_address); + + factory->forward_queries = g_slist_prepend (factory->forward_queries, q); + + return q; +} + +static ForwardQuery * +forward_query_lookup (GdmXdmcpDisplayFactory *factory, + GdmAddress *address) +{ + GSList *li; + GSList *qlist; + ForwardQuery *ret; + time_t curtime; + + curtime = time (NULL); + ret = NULL; + + qlist = g_slist_copy (factory->forward_queries); + + for (li = qlist; li != NULL; li = li->next) { + ForwardQuery *q; + char *host; + char *serv; + + q = (ForwardQuery *) li->data; + + if (q == NULL) { + continue; + } + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (q->dsp_address, &host, &serv); + + g_debug ("GdmXdmcpDisplayFactory: comparing %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + if (gdm_address_equal (q->dsp_address, address)) { + ret = q; + g_free (host); + g_free (serv); + break; + } + + if (q->acctime > 0 && curtime > q->acctime + GDM_FORWARD_QUERY_TIMEOUT) { + g_debug ("GdmXdmcpDisplayFactory: Disposing stale forward query from %s:%s", + host ? host : "(null)", serv ? serv : "(null)"); + + forward_query_destroy (factory, q); + } + + g_free (host); + g_free (serv); + } + + g_slist_free (qlist); + + if (ret == NULL) { + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Host %s not found", + host ? host : "(null)"); + g_free (host); + } + + return ret; +} + +static gboolean +create_address_from_request (ARRAY8 *req_addr, + ARRAY8 *req_port, + int family, + GdmAddress **address) +{ + uint16_t port; + char host_buf [NI_MAXHOST]; + char serv_buf [NI_MAXSERV]; + char *serv; + const char *host; + struct addrinfo hints; + struct addrinfo *ai_list; + struct addrinfo *ai; + int gaierr; + gboolean found; + + if (address != NULL) { + *address = NULL; + } + + if (req_addr == NULL) { + return FALSE; + } + + serv = NULL; + if (req_port != NULL) { + /* port must always be length 2 */ + if (req_port->length != 2) { + return FALSE; + } + + memcpy (&port, req_port->data, 2); + snprintf (serv_buf, sizeof (serv_buf), "%d", ntohs (port)); + serv = serv_buf; + } else { + /* assume XDM_UDP_PORT */ + snprintf (serv_buf, sizeof (serv_buf), "%d", XDM_UDP_PORT); + serv = serv_buf; + } + + host = NULL; + if (req_addr->length == 4) { + host = inet_ntop (AF_INET, + (const void *)req_addr->data, + host_buf, + sizeof (host_buf)); + } else if (req_addr->length == 16) { + host = inet_ntop (AF_INET6, + (const void *)req_addr->data, + host_buf, + sizeof (host_buf)); + } + + if (host == NULL) { + g_warning ("Bad address"); + return FALSE; + } + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = family; + /* this should convert IPv4 address to IPv6 if needed */ +#ifdef AI_V4MAPPED + hints.ai_flags = AI_V4MAPPED; +#endif + hints.ai_socktype = SOCK_DGRAM; + + if ((gaierr = getaddrinfo (host, serv, &hints, &ai_list)) != 0) { + g_warning ("Unable to get address: %s", gai_strerror (gaierr)); + return FALSE; + } + + /* just take the first one */ + ai = ai_list; + + found = FALSE; + if (ai != NULL) { + found = TRUE; + if (address != NULL) { + *address = gdm_address_new_from_sockaddr (ai->ai_addr, ai->ai_addrlen); + } + } + + freeaddrinfo (ai_list); + + return found; +} + +static void +gdm_xdmcp_whack_queued_managed_forwards (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + GSList *li; + + for (li = factory->managed_forwards; li != NULL; li = li->next) { + ManagedForward *mf = li->data; + + if (gdm_address_equal (mf->manager, address) && + gdm_address_equal (mf->origin, origin)) { + factory->managed_forwards = g_slist_remove_link (factory->managed_forwards, li); + g_slist_free_1 (li); + g_source_remove (mf->handler); + /* mf freed by glib */ + return; + } + } +} + +static void +gdm_xdmcp_handle_forward_query (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + ARRAY8 clnt_addr; + ARRAY8 clnt_port; + ARRAYofARRAY8 clnt_authlist; + int i; + int explen; + GdmAddress *disp_address; + char *host; + char *serv; + + disp_address = NULL; + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + char *host2; + + host2 = NULL; + gdm_address_get_numeric_info (address, &host2, NULL); + + g_warning ("%s: Got FORWARD_QUERY from banned host %s", + "gdm_xdmcp_handle_forward query", + host2 ? host2 : "(null)"); + g_free (host2); + return; + } + + /* Read display address */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_addr)) { + g_warning ("%s: Could not read display address", + "gdm_xdmcp_handle_forward_query"); + return; + } + + /* Read display port */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_port)) { + XdmcpDisposeARRAY8 (&clnt_addr); + g_warning ("%s: Could not read display port number", + "gdm_xdmcp_handle_forward_query"); + return; + } + + /* Extract array of authentication names from Xdmcp packet */ + if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authlist)) { + XdmcpDisposeARRAY8 (&clnt_addr); + XdmcpDisposeARRAY8 (&clnt_port); + g_warning ("%s: Could not extract authlist from packet", + "gdm_xdmcp_handle_forward_query"); + return; + } + + /* Crude checksumming */ + explen = 1; + explen += 2 + clnt_addr.length; + explen += 2 + clnt_port.length; + + for (i = 0 ; i < clnt_authlist.length ; i++) { + char *s = g_strndup ((char *) clnt_authlist.data[i].data, + clnt_authlist.length); + g_debug ("GdmXdmcpDisplayFactory: authlist: %s", s); + g_free (s); + + explen += 2 + clnt_authlist.data[i].length; + } + + if G_UNLIKELY (len != explen) { + g_warning ("%s: Error in checksum", + "gdm_xdmcp_handle_forward_query"); + goto out; + } + + if (! create_address_from_request (&clnt_addr, &clnt_port, gdm_address_get_family_type (address), &disp_address)) { + g_warning ("Unable to parse address for request"); + goto out; + } + + gdm_xdmcp_whack_queued_managed_forwards (factory, + address, + disp_address); + + host = NULL; + serv = NULL; + gdm_address_get_numeric_info (disp_address, &host, &serv); + g_debug ("GdmXdmcpDisplayFactory: Got FORWARD_QUERY for display: %s, port %s", + host ? host : "(null)", serv ? serv : "(null)"); + g_free (host); + g_free (serv); + + /* Check with tcp_wrappers if display is allowed to access */ + if (gdm_xdmcp_host_allow (disp_address)) { + ForwardQuery *q; + + q = forward_query_lookup (factory, disp_address); + if (q != NULL) { + forward_query_destroy (factory, q); + } + + forward_query_create (factory, address, disp_address); + + gdm_xdmcp_send_willing (factory, disp_address); + } + + out: + + gdm_address_free (disp_address); + + XdmcpDisposeARRAYofARRAY8 (&clnt_authlist); + XdmcpDisposeARRAY8 (&clnt_port); + XdmcpDisposeARRAY8 (&clnt_addr); +} + +static void +gdm_xdmcp_really_send_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + ARRAY8 addr; + XdmcpHeader header; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending MANAGED_FORWARD to %s", + host ? host : "(null)"); + g_free (host); + + set_address_for_request (origin, &addr); + + header.opcode = (CARD16) GDM_XDMCP_MANAGED_FORWARD; + header.length = 4 + addr.length; + header.version = GDM_XDMCP_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + XdmcpWriteARRAY8 (&factory->buf, &addr); + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + g_free (addr.data); +} + +static gboolean +managed_forward_handler (ManagedForward *mf) +{ + if (mf->xdmcp_display_factory->socket_fd > 0) { + gdm_xdmcp_really_send_managed_forward (mf->xdmcp_display_factory, + mf->manager, + mf->origin); + } + + mf->times++; + if (mf->xdmcp_display_factory->socket_fd <= 0 || mf->times >= 2) { + mf->xdmcp_display_factory->managed_forwards = g_slist_remove (mf->xdmcp_display_factory->managed_forwards, mf); + mf->handler = 0; + /* mf freed by glib */ + return FALSE; + } + return TRUE; +} + +static void +managed_forward_free (ManagedForward *mf) +{ + gdm_address_free (mf->origin); + gdm_address_free (mf->manager); + g_free (mf); +} + +static void +gdm_xdmcp_send_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + ManagedForward *mf; + + gdm_xdmcp_really_send_managed_forward (factory, address, origin); + + mf = g_new0 (ManagedForward, 1); + mf->times = 0; + mf->xdmcp_display_factory = factory; + + mf->manager = gdm_address_copy (address); + mf->origin = gdm_address_copy (origin); + + mf->handler = g_timeout_add_full (G_PRIORITY_DEFAULT, + MANAGED_FORWARD_INTERVAL, + (GSourceFunc)managed_forward_handler, + mf, + (GDestroyNotify)managed_forward_free); + factory->managed_forwards = g_slist_prepend (factory->managed_forwards, mf); +} + +static void +gdm_xdmcp_send_got_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + GdmAddress *origin) +{ + ARRAY8 addr; + XdmcpHeader header; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending GOT_MANAGED_FORWARD to %s", + host ? host : "(null)"); + g_free (host); + + set_address_for_request (origin, &addr); + + header.opcode = (CARD16) GDM_XDMCP_GOT_MANAGED_FORWARD; + header.length = 4 + addr.length; + header.version = GDM_XDMCP_PROTOCOL_VERSION; + XdmcpWriteHeader (&factory->buf, &header); + + XdmcpWriteARRAY8 (&factory->buf, &addr); + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); +} + +static void +count_sessions (const char *id, + GdmDisplay *display, + GdmXdmcpDisplayFactory *factory) +{ + if (GDM_IS_XDMCP_DISPLAY (display)) { + int status; + + status = gdm_display_get_status (display); + + if (status == GDM_DISPLAY_MANAGED) { + factory->num_sessions++; + } else if (status == GDM_DISPLAY_UNMANAGED) { + factory->num_pending_sessions++; + } + } +} + +static void +gdm_xdmcp_recount_sessions (GdmXdmcpDisplayFactory *factory) +{ + GdmDisplayStore *store; + + factory->num_sessions = 0; + factory->num_pending_sessions = 0; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_foreach (store, + (GdmDisplayStoreFunc)count_sessions, + factory); +} + +static gboolean +purge_displays (const char *id, + GdmDisplay *display, + GdmXdmcpDisplayFactory *factory) +{ + if (GDM_IS_XDMCP_DISPLAY (display)) { + int status; + time_t currtime; + time_t acctime; + + currtime = time (NULL); + status = gdm_display_get_status (display); + acctime = gdm_display_get_creation_time (display); + + if (status == GDM_DISPLAY_UNMANAGED && + currtime > acctime + factory->max_wait) { + /* return TRUE to remove display */ + return TRUE; + } + } + + return FALSE; +} + +static void +gdm_xdmcp_displays_purge (GdmXdmcpDisplayFactory *factory) +{ + GdmDisplayStore *store; + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + gdm_display_store_foreach_remove (store, + (GdmDisplayStoreFunc)purge_displays, + factory); + + gdm_xdmcp_recount_sessions (factory); +} + +typedef struct { + const char *hostname; + int display_num; +} RemoveHostData; + +static gboolean +remove_host (const char *id, + GdmDisplay *display, + RemoveHostData *data) +{ + char *hostname; + int disp_num; + + if (! GDM_IS_XDMCP_DISPLAY (display)) { + return FALSE; + } + + gdm_display_get_remote_hostname (display, &hostname, NULL); + gdm_display_get_x11_display_number (display, &disp_num, NULL); + + if (disp_num == data->display_num && + hostname != NULL && + data->hostname != NULL && + strcmp (hostname, data->hostname) == 0) { + /* return TRUE to remove */ + return TRUE; + } + + return FALSE; +} + +static void +display_dispose_check (GdmXdmcpDisplayFactory *factory, + const char *hostname, + int display_num) +{ + RemoveHostData *data; + GdmDisplayStore *store; + + if (hostname == NULL) { + return; + } + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + + g_debug ("GdmXdmcpDisplayFactory: display_dispose_check (%s:%d)", + hostname ? hostname : "(null)", display_num); + + data = g_new0 (RemoveHostData, 1); + data->hostname = hostname; + data->display_num = display_num; + gdm_display_store_foreach_remove (store, + (GdmDisplayStoreFunc)remove_host, + data); + g_free (data); + + gdm_xdmcp_recount_sessions (factory); +} + +static void +gdm_xdmcp_send_decline (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + const char *reason) +{ + XdmcpHeader header; + ARRAY8 authentype; + ARRAY8 authendata; + ARRAY8 status; + ForwardQuery *fq; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending DECLINE to %s", + host ? host : "(null)"); + g_free (host); + + authentype.data = (CARD8 *) 0; + authentype.length = (CARD16) 0; + + authendata.data = (CARD8 *) 0; + authendata.length = (CARD16) 0; + + status.data = (CARD8 *) reason; + status.length = strlen ((char *) status.data); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) DECLINE; + header.length = 2 + status.length; + header.length += 2 + authentype.length; + header.length += 2 + authendata.length; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteARRAY8 (&factory->buf, &status); + XdmcpWriteARRAY8 (&factory->buf, &authentype); + XdmcpWriteARRAY8 (&factory->buf, &authendata); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + /* Send MANAGED_FORWARD to indicate that the connection + * reached some sort of resolution */ + fq = forward_query_lookup (factory, address); + if (fq != NULL) { + gdm_xdmcp_send_managed_forward (factory, fq->from_address, address); + forward_query_destroy (factory, fq); + } +} + +static void +on_hostname_selected (GdmXdmcpChooserDisplay *display, + const char *hostname, + GdmXdmcpDisplayFactory *factory) +{ + struct addrinfo hints; + struct addrinfo *ai_list; + struct addrinfo *ai; + int gaierr; + GdmAddress *address; + IndirectClient *ic; + gchar *xdmcp_port; + + g_debug ("GdmXdmcpDisplayFactory: hostname selected: %s", + hostname ? hostname : "(null)"); + + address = gdm_xdmcp_display_get_remote_address (GDM_XDMCP_DISPLAY (display)); + + g_assert (address != NULL); + + ic = indirect_client_lookup (factory, address); + + if (ic->chosen_address != NULL) { + gdm_address_free (ic->chosen_address); + ic->chosen_address = NULL; + } + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = gdm_address_get_family_type (address); + /* this should convert IPv4 address to IPv6 if needed */ +#ifdef AI_V4MAPPED + hints.ai_flags = AI_V4MAPPED; +#endif + + xdmcp_port = g_strdup_printf ("%d", XDM_UDP_PORT); + if ((gaierr = getaddrinfo (hostname, xdmcp_port, &hints, &ai_list)) != 0) { + g_warning ("Unable to get address: %s", gai_strerror (gaierr)); + g_free (xdmcp_port); + return; + } + g_free (xdmcp_port); + + /* just take the first one */ + ai = ai_list; + + if (ai != NULL) { + char *ip; + ic->chosen_address = gdm_address_new_from_sockaddr (ai->ai_addr, ai->ai_addrlen); + + ip = NULL; + gdm_address_get_numeric_info (ic->chosen_address, &ip, NULL); + g_debug ("GdmXdmcpDisplayFactory: hostname resolves to %s", + ip ? ip : "(null)"); + g_free (ip); + } + + freeaddrinfo (ai_list); +} + +static void +on_client_disconnected (GdmDisplay *display) +{ + if (gdm_display_get_status (display) != GDM_DISPLAY_MANAGED) + return; + + gdm_display_stop_greeter_session (display); + gdm_display_unmanage (display); + gdm_display_finish (display); +} + +static void +on_display_status_changed (GdmDisplay *display, + GParamSpec *arg1, + GdmXdmcpDisplayFactory *factory) +{ + int status; + GdmLaunchEnvironment *launch_environment; + GdmSession *session; + GdmAddress *address; + gint32 session_number; + int display_number; + + launch_environment = NULL; + g_object_get (display, "launch-environment", &launch_environment, NULL); + + session = NULL; + if (launch_environment != NULL) { + session = gdm_launch_environment_get_session (launch_environment); + } + + status = gdm_display_get_status (display); + + g_debug ("GdmXdmcpDisplayFactory: xdmcp display status changed: %d", status); + switch (status) { + case GDM_DISPLAY_FINISHED: + g_object_get (display, + "remote-address", &address, + "x11-display-number", &display_number, + "session-number", &session_number, + NULL); + gdm_xdmcp_send_alive (factory, address, display_number, session_number); + + gdm_display_factory_queue_purge_displays (GDM_DISPLAY_FACTORY (factory)); + break; + case GDM_DISPLAY_FAILED: + gdm_display_factory_queue_purge_displays (GDM_DISPLAY_FACTORY (factory)); + break; + case GDM_DISPLAY_UNMANAGED: + if (session != NULL) { + g_signal_handlers_disconnect_by_func (G_OBJECT (session), + G_CALLBACK (on_client_disconnected), + display); + } + break; + case GDM_DISPLAY_PREPARED: + break; + case GDM_DISPLAY_MANAGED: + if (session != NULL) { + g_signal_connect_object (G_OBJECT (session), + "client-disconnected", + G_CALLBACK (on_client_disconnected), + display, G_CONNECT_SWAPPED); + g_signal_connect_object (G_OBJECT (session), + "disconnected", + G_CALLBACK (on_client_disconnected), + display, G_CONNECT_SWAPPED); + } + break; + default: + g_assert_not_reached (); + break; + } +} + +static GdmDisplay * +gdm_xdmcp_display_create (GdmXdmcpDisplayFactory *factory, + const char *hostname, + GdmAddress *address, + int displaynum) +{ + GdmDisplay *display; + GdmDisplayStore *store; + gboolean use_chooser; + + g_debug ("GdmXdmcpDisplayFactory: Creating xdmcp display for %s:%d", + hostname ? hostname : "(null)", displaynum); + + use_chooser = FALSE; + if (factory->honor_indirect) { + IndirectClient *ic; + + ic = indirect_client_lookup (factory, address); + + /* This was an indirect thingie and nothing was yet chosen, + * use a chooser */ + if (ic != NULL && ic->chosen_address == NULL) { + use_chooser = TRUE; + } + } + + if (use_chooser) { + display = gdm_xdmcp_chooser_display_new (hostname, + displaynum, + address, + get_next_session_serial (factory)); + g_signal_connect (display, "hostname-selected", G_CALLBACK (on_hostname_selected), factory); + } else { + display = gdm_xdmcp_display_new (hostname, + displaynum, + address, + get_next_session_serial (factory)); + } + + if (display == NULL) { + goto out; + } + + if (! gdm_display_prepare (display)) { + gdm_display_unmanage (display); + g_object_unref (display); + display = NULL; + goto out; + } + + g_signal_connect_after (display, + "notify::status", + G_CALLBACK (on_display_status_changed), + factory); + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + gdm_display_store_add (store, display); + + factory->num_pending_sessions++; + out: + + return display; +} + +static void +gdm_xdmcp_send_accept (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD32 session_id, + ARRAY8Ptr authentication_name, + ARRAY8Ptr authentication_data, + ARRAY8Ptr authorization_name, + ARRAY8Ptr authorization_data) +{ + XdmcpHeader header; + char *host; + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) ACCEPT; + header.length = 4; + header.length += 2 + authentication_name->length; + header.length += 2 + authentication_data->length; + header.length += 2 + authorization_name->length; + header.length += 2 + authorization_data->length; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD32 (&factory->buf, session_id); + XdmcpWriteARRAY8 (&factory->buf, authentication_name); + XdmcpWriteARRAY8 (&factory->buf, authentication_data); + XdmcpWriteARRAY8 (&factory->buf, authorization_name); + XdmcpWriteARRAY8 (&factory->buf, authorization_data); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Sending ACCEPT to %s with SessionID=%ld", + host ? host : "(null)", + (long)session_id); + g_free (host); +} + +static void +gdm_xdmcp_handle_request (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + CARD16 clnt_dspnum; + ARRAY16 clnt_conntyp; + ARRAYofARRAY8 clnt_addr; + ARRAY8 clnt_authname; + ARRAY8 clnt_authdata; + ARRAYofARRAY8 clnt_authorization_names; + ARRAY8 clnt_manufacturer; + int explen; + int i; + gboolean mitauth; + gboolean entered; + char *hostname; + + mitauth = FALSE; + entered = FALSE; + + hostname = NULL; + gdm_address_get_numeric_info (address, &hostname, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got REQUEST from %s", + hostname ? hostname : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got REQUEST from banned host %s", + "gdm_xdmcp_handle_request", + hostname ? hostname : "(null)"); + goto out; + } + + gdm_xdmcp_displays_purge (factory); /* Purge pending displays */ + + /* Remote display number */ + if G_UNLIKELY (! XdmcpReadCARD16 (&factory->buf, &clnt_dspnum)) { + g_warning ("%s: Could not read Display Number", + "gdm_xdmcp_handle_request"); + goto out; + } + + /* We don't care about connection type. Address says it all */ + if G_UNLIKELY (! XdmcpReadARRAY16 (&factory->buf, &clnt_conntyp)) { + g_warning ("%s: Could not read Connection Type", + "gdm_xdmcp_handle_request"); + goto out; + } + + /* This is TCP/IP - we don't care */ + if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_addr)) { + g_warning ("%s: Could not read Client Address", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + /* Read authentication type */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_authname)) { + g_warning ("%s: Could not read Authentication Names", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + /* Read authentication data */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_authdata)) { + g_warning ("%s: Could not read Authentication Data", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAY16 (&clnt_conntyp); + XdmcpDisposeARRAY8 (&clnt_authname); + goto out; + } + + /* Read and select from supported authorization list */ + if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&factory->buf, &clnt_authorization_names)) { + g_warning ("%s: Could not read Authorization List", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAY16 (&clnt_conntyp); + XdmcpDisposeARRAY8 (&clnt_authname); + goto out; + } + + /* libXdmcp doesn't terminate strings properly so we cheat and use strncmp () */ + for (i = 0 ; i < clnt_authorization_names.length ; i++) { + if (clnt_authorization_names.data[i].length == 18 && + strncmp ((char *) clnt_authorization_names.data[i].data, "MIT-MAGIC-COOKIE-1", 18) == 0) { + mitauth = TRUE; + } + } + + /* Manufacturer ID */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_manufacturer)) { + g_warning ("%s: Could not read Manufacturer ID", + "gdm_xdmcp_handle_request"); + XdmcpDisposeARRAY8 (&clnt_authname); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAYofARRAY8 (&clnt_authorization_names); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + /* Crude checksumming */ + explen = 2; /* Display Number */ + explen += 1 + 2 * clnt_conntyp.length; /* Connection Type */ + explen += 1; /* Connection Address */ + for (i = 0 ; i < clnt_addr.length ; i++) { + explen += 2 + clnt_addr.data[i].length; + } + explen += 2 + clnt_authname.length; /* Authentication Name */ + explen += 2 + clnt_authdata.length; /* Authentication Data */ + explen += 1; /* Authorization Names */ + for (i = 0 ; i < clnt_authorization_names.length ; i++) { + explen += 2 + clnt_authorization_names.data[i].length; + } + + explen += 2 + clnt_manufacturer.length; + + if G_UNLIKELY (explen != len) { + g_warning ("%s: Failed checksum from %s", + "gdm_xdmcp_handle_request", + hostname ? hostname : "(null)"); + + XdmcpDisposeARRAY8 (&clnt_authname); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAY8 (&clnt_manufacturer); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAYofARRAY8 (&clnt_authorization_names); + XdmcpDisposeARRAY16 (&clnt_conntyp); + goto out; + } + + { + char *s = g_strndup ((char *) clnt_manufacturer.data, clnt_manufacturer.length); + g_debug ("GdmXdmcpDisplayFactory: xdmcp_pending=%d, MaxPending=%d, xdmcp_sessions=%d, MaxSessions=%d, ManufacturerID=%s", + factory->num_pending_sessions, + factory->max_pending_displays, + factory->num_sessions, + factory->max_displays, + s != NULL ? s : ""); + g_free (s); + } + + /* Check if ok to manage display */ + if (mitauth && + factory->num_sessions < factory->max_displays && + (gdm_address_is_local (address) || + gdm_xdmcp_num_displays_from_host (factory, address) < factory->max_displays_per_host)) { + entered = TRUE; + } + + if (entered) { + + /* Check if we are already talking to this host */ + display_dispose_check (factory, hostname, clnt_dspnum); + + if (factory->num_pending_sessions >= factory->max_pending_displays) { + g_debug ("GdmXdmcpDisplayFactory: maximum pending"); + /* Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii */ + gdm_xdmcp_send_decline (factory, address, "Maximum pending servers"); + } else { + GdmDisplay *display; + + display = gdm_xdmcp_display_create (factory, + hostname, + address, + clnt_dspnum); + + if (display != NULL) { + ARRAY8 authentication_name; + ARRAY8 authentication_data; + ARRAY8 authorization_name; + ARRAY8 authorization_data; + gint32 session_number; + const char *x11_cookie; + gsize x11_cookie_size; + char *name; + + x11_cookie = NULL; + x11_cookie_size = 0; + gdm_display_get_x11_cookie (display, &x11_cookie, &x11_cookie_size, NULL); + + name = NULL; + gdm_display_get_x11_display_name (display, &name, NULL); + + g_debug ("GdmXdmcpDisplayFactory: Sending authorization key for display %s", name ? name : "(null)"); + g_free (name); + + g_debug ("GdmXdmcpDisplayFactory: cookie len %d", (int) x11_cookie_size); + + session_number = gdm_xdmcp_display_get_session_number (GDM_XDMCP_DISPLAY (display)); + + /* the send accept will fail if cookie is null */ + g_assert (x11_cookie != NULL); + + authentication_name.data = NULL; + authentication_name.length = 0; + authentication_data.data = NULL; + authentication_data.length = 0; + + authorization_name.data = (CARD8 *) "MIT-MAGIC-COOKIE-1"; + authorization_name.length = strlen ((char *) authorization_name.data); + + authorization_data.data = (CARD8 *) x11_cookie; + authorization_data.length = x11_cookie_size; + + /* the addrs are NOT copied */ + gdm_xdmcp_send_accept (factory, + address, + session_number, + &authentication_name, + &authentication_data, + &authorization_name, + &authorization_data); + } + } + } else { + /* Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii */ + if ( ! mitauth) { + gdm_xdmcp_send_decline (factory, + address, + "Only MIT-MAGIC-COOKIE-1 supported"); + } else if (factory->num_sessions >= factory->max_displays) { + g_warning ("Maximum number of open XDMCP sessions reached"); + gdm_xdmcp_send_decline (factory, + address, + "Maximum number of open sessions reached"); + } else { + g_debug ("GdmXdmcpDisplayFactory: Maximum number of open XDMCP sessions from host %s reached", + hostname ? hostname : "(null)"); + gdm_xdmcp_send_decline (factory, + address, + "Maximum number of open sessions from your host reached"); + } + } + + XdmcpDisposeARRAY8 (&clnt_authname); + XdmcpDisposeARRAY8 (&clnt_authdata); + XdmcpDisposeARRAY8 (&clnt_manufacturer); + XdmcpDisposeARRAYofARRAY8 (&clnt_addr); + XdmcpDisposeARRAYofARRAY8 (&clnt_authorization_names); + XdmcpDisposeARRAY16 (&clnt_conntyp); + out: + g_free (hostname); +} + +static gboolean +lookup_by_session_id (const char *id, + GdmDisplay *display, + gpointer data) +{ + CARD32 sessid; + CARD32 session_id; + + sessid = GPOINTER_TO_INT (data); + + if (! GDM_IS_XDMCP_DISPLAY (display)) { + return FALSE; + } + + session_id = gdm_xdmcp_display_get_session_number (GDM_XDMCP_DISPLAY (display)); + + if (session_id == sessid) { + return TRUE; + } + + return FALSE; +} + +static GdmDisplay * +gdm_xdmcp_display_lookup (GdmXdmcpDisplayFactory *factory, + CARD32 sessid) +{ + GdmDisplay *display; + GdmDisplayStore *store; + + if (sessid == 0) { + return NULL; + } + + store = gdm_display_factory_get_display_store (GDM_DISPLAY_FACTORY (factory)); + display = gdm_display_store_find (store, + (GdmDisplayStoreFunc)lookup_by_session_id, + GINT_TO_POINTER (sessid)); + + return display; +} + +static void +gdm_xdmcp_send_failed (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD32 sessid) +{ + XdmcpHeader header; + ARRAY8 status; + + g_debug ("GdmXdmcpDisplayFactory: Sending FAILED to %ld", (long)sessid); + + /* + * Don't translate, this goes over the wire to servers where we + * don't know the charset or language, so it must be ascii + */ + status.data = (CARD8 *) "Failed to start session"; + status.length = strlen ((char *) status.data); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) FAILED; + header.length = 6 + status.length; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD32 (&factory->buf, sessid); + XdmcpWriteARRAY8 (&factory->buf, &status); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); +} + +static void +gdm_xdmcp_send_refuse (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD32 sessid) +{ + XdmcpHeader header; + ForwardQuery *fq; + + g_debug ("GdmXdmcpDisplayFactory: Sending REFUSE to %ld", + (long)sessid); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) REFUSE; + header.length = 4; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD32 (&factory->buf, sessid); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); + + /* + * This was from a forwarded query quite apparently so + * send MANAGED_FORWARD + */ + fq = forward_query_lookup (factory, address); + if (fq != NULL) { + gdm_xdmcp_send_managed_forward (factory, fq->from_address, address); + forward_query_destroy (factory, fq); + } +} + +static void +gdm_xdmcp_handle_manage (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + CARD32 clnt_sessid; + CARD16 clnt_dspnum; + ARRAY8 clnt_dspclass; + GdmDisplay *display; + ForwardQuery *fq; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got MANAGE from %s", + host ? host : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got Manage from banned host %s", + "gdm_xdmcp_handle_manage", + host ? host : "(null)"); + g_free (host); + return; + } + + /* SessionID */ + if G_UNLIKELY (! XdmcpReadCARD32 (&factory->buf, &clnt_sessid)) { + g_warning ("%s: Could not read Session ID", + "gdm_xdmcp_handle_manage"); + goto out; + } + + /* Remote display number */ + if G_UNLIKELY (! XdmcpReadCARD16 (&factory->buf, &clnt_dspnum)) { + g_warning ("%s: Could not read Display Number", + "gdm_xdmcp_handle_manage"); + goto out; + } + + /* Display Class */ + if G_UNLIKELY (! XdmcpReadARRAY8 (&factory->buf, &clnt_dspclass)) { + g_warning ("%s: Could not read Display Class", + "gdm_xdmcp_handle_manage"); + goto out; + } + + { + char *s = g_strndup ((char *) clnt_dspclass.data, clnt_dspclass.length); + g_debug ("GdmXdmcpDisplayFactory: Got display=%d, SessionID=%ld Class=%s from %s", + (int)clnt_dspnum, + (long)clnt_sessid, + s != NULL ? s : "", + host); + + g_free (s); + } + + display = gdm_xdmcp_display_lookup (factory, clnt_sessid); + if (display != NULL && + gdm_display_get_status (display) == GDM_DISPLAY_PREPARED) { + char *name; + + name = NULL; + gdm_display_get_x11_display_name (display, &name, NULL); + g_debug ("GdmXdmcpDisplayFactory: Looked up %s", + name ? name : "(null)"); + g_free (name); + + if (factory->honor_indirect) { + IndirectClient *ic; + + ic = indirect_client_lookup (factory, address); + + /* This was an indirect thingie and nothing was yet chosen, + * use a chooser */ + if (ic != NULL && ic->chosen_address == NULL) { + g_debug ("GdmXdmcpDisplayFactory: use chooser"); + /*d->use_chooser = TRUE; + d->indirect_id = ic->id;*/ + } else { + /*d->indirect_id = 0; + d->use_chooser = FALSE;*/ + if (ic != NULL) { + indirect_client_destroy (factory, ic); + } + } + } else { + + } + + /* this was from a forwarded query quite apparently so + * send MANAGED_FORWARD */ + fq = forward_query_lookup (factory, address); + if (fq != NULL) { + gdm_xdmcp_send_managed_forward (factory, fq->from_address, address); + forward_query_destroy (factory, fq); + } + + factory->num_sessions++; + factory->num_pending_sessions--; + + /* Start greeter/session */ + if (! gdm_display_manage (display)) { + gdm_xdmcp_send_failed (factory, address, clnt_sessid); + g_debug ("GdmXdmcpDisplayFactory: Failed to manage display"); + } + } else if (display != NULL && + gdm_display_get_status (display) == GDM_DISPLAY_MANAGED) { + g_debug ("GdmXdmcpDisplayFactory: Session ID %ld already managed", + (long)clnt_sessid); + } else { + g_warning ("GdmXdmcpDisplayFactory: Failed to look up session ID %ld", + (long)clnt_sessid); + gdm_xdmcp_send_refuse (factory, address, clnt_sessid); + } + + out: + XdmcpDisposeARRAY8 (&clnt_dspclass); + g_free (host); +} + +static void +gdm_xdmcp_handle_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + ARRAY8 clnt_address; + char *host; + GdmAddress *disp_address; + IndirectClient *ic; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got MANAGED_FORWARD from %s", + host ? host : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("GdmXdmcpDisplayFactory: Got MANAGED_FORWARD from banned host %s", + host ? host : "(null)"); + g_free (host); + return; + } + g_free (host); + + /* Hostname */ + if G_UNLIKELY ( ! XdmcpReadARRAY8 (&factory->buf, &clnt_address)) { + g_warning ("%s: Could not read address", + "gdm_xdmcp_handle_managed_forward"); + return; + } + + disp_address = NULL; + if (! create_address_from_request (&clnt_address, NULL, gdm_address_get_family_type (address), &disp_address)) { + g_warning ("Unable to parse address for request"); + XdmcpDisposeARRAY8 (&clnt_address); + return; + } + + ic = indirect_client_lookup_by_chosen (factory, address, disp_address); + if (ic != NULL) { + indirect_client_destroy (factory, ic); + } + + /* Note: we send GOT even on not found, just in case our previous + * didn't get through and this was a second managed forward */ + gdm_xdmcp_send_got_managed_forward (factory, address, disp_address); + + gdm_address_free (disp_address); + + XdmcpDisposeARRAY8 (&clnt_address); +} + +static void +gdm_xdmcp_handle_got_managed_forward (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + GdmAddress *disp_address; + ARRAY8 clnt_address; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got MANAGED_FORWARD from %s", + host ? host : "(null)"); + + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got GOT_MANAGED_FORWARD from banned host %s", + "gdm_xdmcp_handle_request", host ? host : "(null)"); + g_free (host); + return; + } + g_free (host); + + /* Hostname */ + if G_UNLIKELY ( ! XdmcpReadARRAY8 (&factory->buf, &clnt_address)) { + g_warning ("%s: Could not read address", + "gdm_xdmcp_handle_got_managed_forward"); + return; + } + + if (! create_address_from_request (&clnt_address, NULL, gdm_address_get_family_type (address), &disp_address)) { + g_warning ("%s: Could not read address", + "gdm_xdmcp_handle_got_managed_forward"); + XdmcpDisposeARRAY8 (&clnt_address); + return; + } + + gdm_xdmcp_whack_queued_managed_forwards (factory, address, disp_address); + + gdm_address_free (disp_address); + + XdmcpDisposeARRAY8 (&clnt_address); +} + +static void +gdm_xdmcp_send_alive (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + CARD16 dspnum, + CARD32 sessid) +{ + XdmcpHeader header; + GdmDisplay *display; + int send_running = 0; + CARD32 send_sessid = 0; + + display = gdm_xdmcp_display_lookup (factory, sessid); + if (display == NULL) { + display = gdm_xdmcp_display_lookup_by_host (factory, address, dspnum); + } + + if (display != NULL) { + int status; + + send_sessid = gdm_xdmcp_display_get_session_number (GDM_XDMCP_DISPLAY (display)); + status = gdm_display_get_status (display); + + if (status == GDM_DISPLAY_MANAGED) { + send_running = 1; + } + } + + g_debug ("GdmXdmcpDisplayFactory: Sending ALIVE to %ld (running %d, sessid %ld)", + (long)sessid, + send_running, + (long)send_sessid); + + header.version = XDM_PROTOCOL_VERSION; + header.opcode = (CARD16) ALIVE; + header.length = 5; + + XdmcpWriteHeader (&factory->buf, &header); + XdmcpWriteCARD8 (&factory->buf, send_running); + XdmcpWriteCARD32 (&factory->buf, send_sessid); + + XdmcpFlush (factory->socket_fd, + &factory->buf, + (XdmcpNetaddr)gdm_address_peek_sockaddr_storage (address), + (int)gdm_sockaddr_len (gdm_address_peek_sockaddr_storage (address))); +} + +static void +gdm_xdmcp_handle_keepalive (GdmXdmcpDisplayFactory *factory, + GdmAddress *address, + int len) +{ + CARD16 clnt_dspnum; + CARD32 clnt_sessid; + char *host; + + host = NULL; + gdm_address_get_numeric_info (address, &host, NULL); + g_debug ("GdmXdmcpDisplayFactory: Got KEEPALIVE from %s", + host ? host : "(null)"); + + /* Check with tcp_wrappers if client is allowed to access */ + if (! gdm_xdmcp_host_allow (address)) { + g_warning ("%s: Got KEEPALIVE from banned host %s", + "gdm_xdmcp_handle_keepalive", + host ? host : "(null)"); + g_free (host); + return; + } + g_free (host); + + /* Remote display number */ + if G_UNLIKELY (! XdmcpReadCARD16 (&factory->buf, &clnt_dspnum)) { + g_warning ("%s: Could not read Display Number", + "gdm_xdmcp_handle_keepalive"); + return; + } + + /* SessionID */ + if G_UNLIKELY (! XdmcpReadCARD32 (&factory->buf, &clnt_sessid)) { + g_warning ("%s: Could not read Session ID", + "gdm_xdmcp_handle_keepalive"); + return; + } + + gdm_xdmcp_send_alive (factory, address, clnt_dspnum, clnt_sessid); +} + +static const char * +opcode_string (int opcode) +{ + static const char * const opcode_names[] = { + NULL, + "BROADCAST_QUERY", + "QUERY", + "INDIRECT_QUERY", + "FORWARD_QUERY", + "WILLING", + "UNWILLING", + "REQUEST", + "ACCEPT", + "DECLINE", + "MANAGE", + "REFUSE", + "FAILED", + "KEEPALIVE", + "ALIVE" + }; + static const char * const gdm_opcode_names[] = { + "MANAGED_FORWARD", + "GOT_MANAGED_FORWARD" + }; + + + if (opcode < G_N_ELEMENTS (opcode_names)) { + return opcode_names [opcode]; + } else if (opcode >= GDM_XDMCP_FIRST_OPCODE && + opcode < GDM_XDMCP_LAST_OPCODE) { + return gdm_opcode_names [opcode - GDM_XDMCP_FIRST_OPCODE]; + } else { + return "UNKNOWN"; + } +} + +static gboolean +decode_packet (GIOChannel *source, + GIOCondition cond, + GdmXdmcpDisplayFactory *factory) +{ + struct sockaddr_storage clnt_ss; + GdmAddress *address; + gint ss_len; + XdmcpHeader header; + char *host; + char *port; + int res; + + g_debug ("GdmXdmcpDisplayFactory: decode_packet: GIOCondition %d", (int)cond); + + if ( ! (cond & G_IO_IN)) { + return TRUE; + } + + ss_len = (int) sizeof (clnt_ss); + + res = XdmcpFill (factory->socket_fd, &factory->buf, (XdmcpNetaddr)&clnt_ss, &ss_len); + if G_UNLIKELY (! res) { + g_debug ("GdmXdmcpDisplayFactory: Could not create XDMCP buffer!"); + return TRUE; + } + + res = XdmcpReadHeader (&factory->buf, &header); + if G_UNLIKELY (! res) { + g_warning ("GdmXdmcpDisplayFactory: Could not read XDMCP header!"); + return TRUE; + } + + if G_UNLIKELY (header.version != XDM_PROTOCOL_VERSION && + header.version != GDM_XDMCP_PROTOCOL_VERSION) { + g_warning ("XDMCP: Incorrect XDMCP version!"); + return TRUE; + } + + address = gdm_address_new_from_sockaddr ((struct sockaddr *) &clnt_ss, ss_len); + if (address == NULL) { + g_warning ("XDMCP: Unable to parse address"); + return TRUE; + } + + gdm_address_debug (address); + + host = NULL; + port = NULL; + gdm_address_get_numeric_info (address, &host, &port); + + g_debug ("GdmXdmcpDisplayFactory: Received opcode %s from client %s : %s", + opcode_string (header.opcode), + host ? host : "(null)", + port ? port : "(null)"); + + switch (header.opcode) { + case BROADCAST_QUERY: + gdm_xdmcp_handle_broadcast_query (factory, address, header.length); + break; + + case QUERY: + gdm_xdmcp_handle_query (factory, address, header.length); + break; + + case INDIRECT_QUERY: + gdm_xdmcp_handle_indirect_query (factory, address, header.length); + break; + + case FORWARD_QUERY: + gdm_xdmcp_handle_forward_query (factory, address, header.length); + break; + + case REQUEST: + gdm_xdmcp_handle_request (factory, address, header.length); + break; + + case MANAGE: + gdm_xdmcp_handle_manage (factory, address, header.length); + break; + + case KEEPALIVE: + gdm_xdmcp_handle_keepalive (factory, address, header.length); + break; + + case GDM_XDMCP_MANAGED_FORWARD: + gdm_xdmcp_handle_managed_forward (factory, address, header.length); + break; + + case GDM_XDMCP_GOT_MANAGED_FORWARD: + gdm_xdmcp_handle_got_managed_forward (factory, address, header.length); + break; + + default: + g_debug ("GdmXdmcpDisplayFactory: Unknown opcode from client %s : %s", + host ? host : "(null)", + port ? port : "(null)"); + + break; + } + + g_free (host); + g_free (port); + + gdm_address_free (address); + + return TRUE; +} + +static gboolean +gdm_xdmcp_display_factory_start (GdmDisplayFactory *base_factory) +{ + gboolean ret; + GIOChannel *ioc; + GdmXdmcpDisplayFactory *factory = GDM_XDMCP_DISPLAY_FACTORY (base_factory); + gboolean res; + + g_return_val_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory), FALSE); + g_return_val_if_fail (factory->socket_fd == -1, FALSE); + + /* read configuration */ + res = gdm_settings_direct_get_uint (GDM_KEY_UDP_PORT, + &(factory->port)); + res = res && gdm_settings_direct_get_boolean (GDM_KEY_MULTICAST, + &(factory->use_multicast)); + res = res && gdm_settings_direct_get_string (GDM_KEY_MULTICAST_ADDR, + &(factory->multicast_address)); + res = res && gdm_settings_direct_get_boolean (GDM_KEY_INDIRECT, + &(factory->honor_indirect)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_DISPLAYS_PER_HOST, + &(factory->max_displays_per_host)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_SESSIONS, + &(factory->max_displays)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_PENDING, + &(factory->max_pending_displays)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_WAIT, + &(factory->max_wait)); + res = res && gdm_settings_direct_get_uint (GDM_KEY_MAX_WAIT_INDIRECT, + &(factory->max_wait_indirect)); + res = res && gdm_settings_direct_get_string (GDM_KEY_WILLING, + &(factory->willing_script)); + + if (! res) { + return res; + } + + ret = open_port (factory); + if (! ret) { + return ret; + } + + g_debug ("GdmXdmcpDisplayFactory: Starting to listen on XDMCP port"); + + ioc = g_io_channel_unix_new (factory->socket_fd); + + g_io_channel_set_encoding (ioc, NULL, NULL); + g_io_channel_set_buffered (ioc, FALSE); + + factory->socket_watch_id = g_io_add_watch_full (ioc, + G_PRIORITY_DEFAULT, + G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, + (GIOFunc)decode_packet, + factory, + NULL); + g_io_channel_unref (ioc); + + return ret; +} + +static gboolean +gdm_xdmcp_display_factory_stop (GdmDisplayFactory *base_factory) +{ + GdmXdmcpDisplayFactory *factory = GDM_XDMCP_DISPLAY_FACTORY (base_factory); + + g_return_val_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory), FALSE); + g_return_val_if_fail (factory->socket_fd != -1, FALSE); + + if (factory->socket_watch_id > 0) { + g_source_remove (factory->socket_watch_id); + factory->socket_watch_id = 0; + } + + if (factory->socket_fd > 0) { + VE_IGNORE_EINTR (close (factory->socket_fd)); + factory->socket_fd = -1; + } + + return TRUE; +} + +void +gdm_xdmcp_display_factory_set_port (GdmXdmcpDisplayFactory *factory, + guint port) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->port = port; +} + +static void +gdm_xdmcp_display_factory_set_use_multicast (GdmXdmcpDisplayFactory *factory, + gboolean use_multicast) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->use_multicast = use_multicast; +} + +static void +gdm_xdmcp_display_factory_set_multicast_address (GdmXdmcpDisplayFactory *factory, + const char *address) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + g_free (factory->multicast_address); + factory->multicast_address = g_strdup (address); +} + +static void +gdm_xdmcp_display_factory_set_honor_indirect (GdmXdmcpDisplayFactory *factory, + gboolean honor_indirect) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->honor_indirect = honor_indirect; +} + +static void +gdm_xdmcp_display_factory_set_max_displays_per_host (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_displays_per_host = num; +} + +static void +gdm_xdmcp_display_factory_set_max_displays (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_displays = num; +} + +static void +gdm_xdmcp_display_factory_set_max_pending_displays (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_pending_displays = num; +} + +static void +gdm_xdmcp_display_factory_set_max_wait (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_wait = num; +} + +static void +gdm_xdmcp_display_factory_set_max_wait_indirect (GdmXdmcpDisplayFactory *factory, + guint num) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + factory->max_wait_indirect = num; +} + +static void +gdm_xdmcp_display_factory_set_willing_script (GdmXdmcpDisplayFactory *factory, + const char *script) +{ + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (factory)); + + g_free (factory->willing_script); + factory->willing_script = g_strdup (script); +} + +static void +gdm_xdmcp_display_factory_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GdmXdmcpDisplayFactory *self; + + self = GDM_XDMCP_DISPLAY_FACTORY (object); + + switch (prop_id) { + case PROP_PORT: + gdm_xdmcp_display_factory_set_port (self, g_value_get_uint (value)); + break; + case PROP_USE_MULTICAST: + gdm_xdmcp_display_factory_set_use_multicast (self, g_value_get_boolean (value)); + break; + case PROP_MULTICAST_ADDRESS: + gdm_xdmcp_display_factory_set_multicast_address (self, g_value_get_string (value)); + break; + case PROP_HONOR_INDIRECT: + gdm_xdmcp_display_factory_set_honor_indirect (self, g_value_get_boolean (value)); + break; + case PROP_MAX_DISPLAYS_PER_HOST: + gdm_xdmcp_display_factory_set_max_displays_per_host (self, g_value_get_uint (value)); + break; + case PROP_MAX_DISPLAYS: + gdm_xdmcp_display_factory_set_max_displays (self, g_value_get_uint (value)); + break; + case PROP_MAX_PENDING_DISPLAYS: + gdm_xdmcp_display_factory_set_max_pending_displays (self, g_value_get_uint (value)); + break; + case PROP_MAX_WAIT: + gdm_xdmcp_display_factory_set_max_wait (self, g_value_get_uint (value)); + break; + case PROP_MAX_WAIT_INDIRECT: + gdm_xdmcp_display_factory_set_max_wait_indirect (self, g_value_get_uint (value)); + break; + case PROP_WILLING_SCRIPT: + gdm_xdmcp_display_factory_set_willing_script (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_xdmcp_display_factory_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GdmXdmcpDisplayFactory *self; + + self = GDM_XDMCP_DISPLAY_FACTORY (object); + + switch (prop_id) { + case PROP_PORT: + g_value_set_uint (value, self->port); + break; + case PROP_USE_MULTICAST: + g_value_set_boolean (value, self->use_multicast); + break; + case PROP_MULTICAST_ADDRESS: + g_value_set_string (value, self->multicast_address); + break; + case PROP_HONOR_INDIRECT: + g_value_set_boolean (value, self->honor_indirect); + break; + case PROP_MAX_DISPLAYS_PER_HOST: + g_value_set_uint (value, self->max_displays_per_host); + break; + case PROP_MAX_DISPLAYS: + g_value_set_uint (value, self->max_displays); + break; + case PROP_MAX_PENDING_DISPLAYS: + g_value_set_uint (value, self->max_pending_displays); + break; + case PROP_MAX_WAIT: + g_value_set_uint (value, self->max_wait); + break; + case PROP_MAX_WAIT_INDIRECT: + g_value_set_uint (value, self->max_wait_indirect); + break; + case PROP_WILLING_SCRIPT: + g_value_set_string (value, self->willing_script); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gdm_xdmcp_display_factory_class_init (GdmXdmcpDisplayFactoryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GdmDisplayFactoryClass *factory_class = GDM_DISPLAY_FACTORY_CLASS (klass); + + object_class->get_property = gdm_xdmcp_display_factory_get_property; + object_class->set_property = gdm_xdmcp_display_factory_set_property; + object_class->finalize = gdm_xdmcp_display_factory_finalize; + + factory_class->start = gdm_xdmcp_display_factory_start; + factory_class->stop = gdm_xdmcp_display_factory_stop; + + g_object_class_install_property (object_class, + PROP_PORT, + g_param_spec_uint ("port", + "UDP port", + "UDP port", + 0, + G_MAXINT, + DEFAULT_PORT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_USE_MULTICAST, + g_param_spec_boolean ("use-multicast", + NULL, + NULL, + DEFAULT_USE_MULTICAST, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MULTICAST_ADDRESS, + g_param_spec_string ("multicast-address", + "multicast-address", + "multicast-address", + DEFAULT_MULTICAST_ADDRESS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_HONOR_INDIRECT, + g_param_spec_boolean ("honor-indirect", + NULL, + NULL, + DEFAULT_HONOR_INDIRECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_WILLING_SCRIPT, + g_param_spec_string ("willing-script", + "willing-script", + "willing-script", + DEFAULT_WILLING_SCRIPT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_DISPLAYS_PER_HOST, + g_param_spec_uint ("max-displays-per-host", + "max-displays-per-host", + "max-displays-per-host", + 0, + G_MAXINT, + DEFAULT_MAX_DISPLAYS_PER_HOST, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_DISPLAYS, + g_param_spec_uint ("max-displays", + "max-displays", + "max-displays", + 0, + G_MAXINT, + DEFAULT_MAX_DISPLAYS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_PENDING_DISPLAYS, + g_param_spec_uint ("max-pending-displays", + "max-pending-displays", + "max-pending-displays", + 0, + G_MAXINT, + DEFAULT_MAX_PENDING_DISPLAYS, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_WAIT, + g_param_spec_uint ("max-wait", + "max-wait", + "max-wait", + 0, + G_MAXINT, + DEFAULT_MAX_WAIT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (object_class, + PROP_MAX_WAIT_INDIRECT, + g_param_spec_uint ("max-wait-indirect", + "max-wait-indirect", + "max-wait-indirect", + 0, + G_MAXINT, + DEFAULT_MAX_WAIT_INDIRECT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); +} + +static void +gdm_xdmcp_display_factory_init (GdmXdmcpDisplayFactory *factory) +{ + char hostbuf[1024]; + struct utsname name; + + factory->socket_fd = -1; + + factory->session_serial = g_random_int (); + + /* Fetch and store local hostname in XDMCP friendly format */ + hostbuf[1023] = '\0'; + if G_UNLIKELY (gethostname (hostbuf, 1023) != 0) { + g_warning ("Could not get server hostname: %s!", g_strerror (errno)); + strcpy (hostbuf, "localhost.localdomain"); + } + + uname (&name); + factory->sysid = g_strconcat (name.sysname, + " ", + name.release, + NULL); + + factory->hostname = g_strdup (hostbuf); + + factory->servhost.data = (CARD8 *) g_strdup (hostbuf); + factory->servhost.length = strlen ((char *) factory->servhost.data); +} + +static void +gdm_xdmcp_display_factory_finalize (GObject *object) +{ + GdmXdmcpDisplayFactory *factory; + + g_return_if_fail (object != NULL); + g_return_if_fail (GDM_IS_XDMCP_DISPLAY_FACTORY (object)); + + factory = GDM_XDMCP_DISPLAY_FACTORY (object); + + g_return_if_fail (factory != NULL); + + if (factory->socket_watch_id > 0) { + g_source_remove (factory->socket_watch_id); + } + + if (factory->socket_fd > 0) { + close (factory->socket_fd); + factory->socket_fd = -1; + } + + g_slist_free (factory->forward_queries); + g_slist_free (factory->managed_forwards); + + g_free (factory->sysid); + g_free (factory->hostname); + g_free (factory->multicast_address); + g_free (factory->willing_script); + + /* FIXME: Free servhost */ + + G_OBJECT_CLASS (gdm_xdmcp_display_factory_parent_class)->finalize (object); +} + +GdmXdmcpDisplayFactory * +gdm_xdmcp_display_factory_new (GdmDisplayStore *store) +{ + if (xdmcp_display_factory_object != NULL) { + g_object_ref (xdmcp_display_factory_object); + } else { + xdmcp_display_factory_object = g_object_new (GDM_TYPE_XDMCP_DISPLAY_FACTORY, + "display-store", store, + NULL); + g_object_add_weak_pointer (xdmcp_display_factory_object, + (gpointer *) &xdmcp_display_factory_object); + } + + return GDM_XDMCP_DISPLAY_FACTORY (xdmcp_display_factory_object); +} |