/* -*- 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;
        }

        g_clear_object (&launch_environment);
}

static GdmDisplay *
gdm_xdmcp_display_create (GdmXdmcpDisplayFactory *factory,
                          const char             *hostname,
                          GdmAddress             *address,
                          int                     displaynum)
{
        GdmDisplay      *display;
        GdmDisplayStore *store;
        gboolean         use_chooser;
        const char      *session_types[] = { "x11", NULL };

        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;
        }

        g_object_set (G_OBJECT (display),
                      "session-type", session_types[0],
                      "supported-session-types", session_types,
                      NULL);

        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);
}
