/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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>
#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-object.h>

#include <X11/Xlib.h>
#include <X11/Xmd.h>
#include <X11/Xdmcp.h>

#include "gdm-common.h"
#include "gdm-xdmcp-manager.h"

#include "misc.h"
#include "auth.h"
#include "cookie.h"
#include "choose.h"
#include "gdm-daemon-config.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 GDM_XDMCP_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GDM_TYPE_XDMCP_MANAGER, GdmXdmcpManagerPrivate))

#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 2
#define DEFAULT_MAX_DISPLAYS          16
#define DEFAULT_MAX_PENDING_DISPLAYS  4
#define DEFAULT_MAX_WAIT              15

#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 _GdmForwardQuery {
	time_t                   acctime;
	struct sockaddr_storage *dsp_sa;
	struct sockaddr_storage *from_sa;
} GdmForwardQuery;

typedef struct {
	int                     times;
	guint                   handler;
	struct sockaddr_storage manager;
	struct sockaddr_storage origin;
	GdmXdmcpManager        *xdmcp_manager;
} ManagedForward;

struct GdmXdmcpManagerPrivate
{
	GSList          *forward_queries;
	GSList          *managed_forwards;

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

enum {
	DISPLAY_ADDED,
	DISPLAY_REMOVED,
	LAST_SIGNAL
};

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

static void	gdm_xdmcp_manager_class_init	(GdmXdmcpManagerClass *klass);
static void	gdm_xdmcp_manager_init	        (GdmXdmcpManager      *manager);
static void	gdm_xdmcp_manager_finalize	(GObject	      *object);

static gpointer xdmcp_manager_object = NULL;

G_DEFINE_TYPE (GdmXdmcpManager, gdm_xdmcp_manager, G_TYPE_OBJECT)

/* 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_manager_error_quark (void)
{
	static GQuark ret = 0;
	if (ret == 0) {
		ret = g_quark_from_static_string ("gdm_xdmcp_manager_error");
	}

	return ret;
}

static gint32
get_next_session_serial (GdmXdmcpManager *manager)
{
	gint32 serial;

 again:
	if (manager->priv->session_serial != G_MAXINT32) {
		serial = manager->priv->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 ");
		}
		if (ai->ai_flags & AI_V4MAPPED) {
			g_string_append (str, "v4mapped ");
		}
		if (ai->ai_flags & AI_ALL) {
			g_string_append (str, "all ");
		}
	}
	return g_string_free (str, FALSE);
}

/* for debugging */
static void
debug_addrinfo (struct addrinfo *ai)
{
	char *str;
	str = ai_flags_str (ai);
	g_debug ("XDMCP: 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 (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);
	if ((gaierr = getaddrinfo (NULL, strport, &hints, &ai_list)) != 0) {
		g_error ("Unable to connect to socket: %s", gai_strerror (gaierr));
		return -1;
	}

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

			gdm_address_get_info ((struct sockaddr_storage *)ai->ai_addr, &host, &serv);
			g_debug ("XDMCP: Attempting to bind to host %s port %s", host, serv);
			g_free (host);
			g_free (serv);
			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 (GdmXdmcpManager *manager)
{
#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 ("XDMCP: 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,
					   manager->priv->multicast_address,
					   &mreq.ipv6mr_multiaddr);

				setsockopt (manager->priv->socket_fd,
					    IPPROTO_IPV6,
					    IPV6_JOIN_GROUP,
					    &mreq,
					    sizeof (mreq));
			}
		}
		g_free (buf);
		close (socktemp);
	}
#endif /* ENABLE_IPV6 */
}

static gboolean
open_port (GdmXdmcpManager *manager)
{
	struct sockaddr_storage serv_sa = { 0 };

	g_debug ("XDMCP: Start up on host %s, port %d",
		 manager->priv->hostname,
		 manager->priv->port);

	/* Open socket for communications */
#ifdef ENABLE_IPV6
	manager->priv->socket_fd = do_bind (manager->priv->port, AF_INET6, &serv_sa);
	if (manager->priv->socket_fd < 0)
#endif
		manager->priv->socket_fd = do_bind (manager->priv->port, AF_INET, &serv_sa);

	if G_UNLIKELY (manager->priv->socket_fd < 0) {
		g_warning (_("Could not create socket!"));
		return FALSE;
	}

	if (manager->priv->use_multicast) {
		setup_multicast (manager);
	}

	return TRUE;
}

static gboolean
gdm_xdmcp_host_allow (struct sockaddr_storage *clnt_sa)
{
#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);

	GdmHostent *client_he;
	char       *client;
	gboolean    ret;
	char       *host;

	/* Find client hostname */
	client_he = gdm_gethostbyaddr (clnt_sa);

	if (client_he->not_found) {
		client = "unknown";
	} else {
		g_debug ("gdm_xdmcp_host_allow: client->hostname is %s\n",
			 client_he->hostname);
		client = client_he->hostname;
	}

	/* Check with tcp_wrappers if client is allowed to access */
	host = NULL;
	gdm_address_get_info (clnt_sa, &host, NULL);
	ret = hosts_ctl ("gdm", client, host, "");
	g_free (host);

	gdm_hostent_free (client_he);

	return ret;
#else /* HAVE_TCPWRAPPERS */
	return (TRUE);
#endif /* HAVE_TCPWRAPPERS */
}

static int
gdm_xdmcp_num_displays_from_host (GdmXdmcpManager         *manager,
				  struct sockaddr_storage *addr)
{
	GSList *li;
	int     count = 0;
	GSList *displays;

	displays = gdm_daemon_config_get_display_list ();

	for (li = displays; li != NULL; li = li->next) {
		GdmDisplay *disp = li->data;
		if (SERVER_IS_XDMCP (disp)) {
			if (gdm_address_equal (&disp->addr, addr)) {
				count++;
			}
		}
	}
	return count;
}

static GdmDisplay *
gdm_xdmcp_display_lookup_by_host (GdmXdmcpManager         *manager,
				  struct sockaddr_storage *addr,
				  int                      dspnum)
{
	GSList *li;
	GSList *displays;

	displays = gdm_daemon_config_get_display_list ();

	for (li = displays; li != NULL; li = li->next) {
		GdmDisplay *disp = li->data;
		if (SERVER_IS_XDMCP (disp)) {

			if (gdm_address_equal (&disp->addr, addr)
			    && disp->xdmcp_dispnum == dspnum) {
				return disp;
			}
		}
	}

	return NULL;
}

static char *
get_willing_output (GdmXdmcpManager *manager)
{
	char  *output;
	char **argv;
	FILE  *fd;
	char   buf[256];

	output = NULL;
	buf[0] = '\0';

	if (manager->priv->willing_script == NULL) {
		goto out;
	}

	argv = NULL;
	if (! g_shell_parse_argv (manager->priv->willing_script, NULL, &argv, NULL)) {
		goto out;
	}

	if (argv == NULL ||
	    argv[0] == NULL ||
	    g_access (argv[0], X_OK) != 0) {
		goto out;
	}

	fd = popen (manager->priv->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 (GdmXdmcpManager         *manager,
			struct sockaddr_storage *clnt_sa)
{
	ARRAY8        status;
	XdmcpHeader   header;
	static char  *last_status = NULL;
	static time_t last_willing = 0;
	char         *host;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("XDMCP: Sending WILLING to %s", host);
	g_free (host);

	if (last_willing == 0 || time (NULL) - 3 > last_willing) {
		char *s;

		g_free (last_status);

		s = get_willing_output (manager);
		if (s != NULL) {
			g_free (last_status);
			last_status = s;
		} else {
			last_status = g_strdup (manager->priv->sysid);
		}
	}

	if (! gdm_address_is_local (clnt_sa) &&
	    gdm_xdmcp_num_displays_from_host (manager, clnt_sa) >= manager->priv->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  += manager->priv->servhost.length + status.length;
	header.version  = XDM_PROTOCOL_VERSION;
	XdmcpWriteHeader (&manager->priv->buf, &header);

	/* Hardcoded authentication */
	XdmcpWriteARRAY8 (&manager->priv->buf, &serv_authlist.authentication);
	XdmcpWriteARRAY8 (&manager->priv->buf, &manager->priv->servhost);
	XdmcpWriteARRAY8 (&manager->priv->buf, &status);

	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));

	g_free (status.data);
}

static void
gdm_xdmcp_send_unwilling (GdmXdmcpManager         *manager,
			  struct sockaddr_storage *clnt_sa,
			  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;
	}

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("XDMCP: Sending UNWILLING to %s", host);
	g_warning (_("Denied XDMCP query from host %s"), host);
	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 + manager->priv->servhost.length + status.length;
	header.version = XDM_PROTOCOL_VERSION;
	XdmcpWriteHeader (&manager->priv->buf, &header);

	XdmcpWriteARRAY8 (&manager->priv->buf, &manager->priv->servhost);
	XdmcpWriteARRAY8 (&manager->priv->buf, &status);
	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));

	last_time = time (NULL);
}

#define SIN(__s)   ((struct sockaddr_in *) __s)
#define SIN6(__s)  ((struct sockaddr_in6 *) __s)

static void
set_port_for_request (struct sockaddr_storage *ss,
		      ARRAY8                  *port)
{
	/* 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 (struct sockaddr_storage *ss,
			 ARRAY8                  *address)
{

	switch (ss->ss_family) {
	case AF_INET:
		address->length = sizeof (struct in_addr);
		address->data = g_memdup (&SIN (ss)->sin_addr, address->length);
		break;
	case AF_INET6:
		address->length = sizeof (struct in6_addr);
		address->data = g_memdup (&SIN6 (ss)->sin6_addr, address->length);
		break;
	default:
		address->length = 0;
		address->data = NULL;
		break;
	}

}

static void
gdm_xdmcp_send_forward_query (GdmXdmcpManager         *manager,
			      GdmIndirectDisplay      *id,
			      struct sockaddr_storage *clnt_sa,
			      struct sockaddr_storage *display_addr,
			      ARRAYofARRAY8Ptr         authlist)
{
	struct sockaddr_storage *sa;
	XdmcpHeader              header;
	int                      i;
	ARRAY8                   address;
	ARRAY8                   port;
	char                    *host;
	char                    *serv;

	g_assert (id != NULL);
	g_assert (id->chosen_host != NULL);

	gdm_address_get_info (id->chosen_host, &host, NULL);
	g_debug ("XDMCP: Sending forward query to %s",
		   host);
	g_free (host);

	gdm_address_get_info (display_addr, &host, &serv);
	g_debug ("gdm_xdmcp_send_forward_query: Query contains %s:%s",
		 host, serv);
	g_free (host);
	g_free (serv);

	set_port_for_request (clnt_sa, &port);
	set_address_for_request (display_addr, &address);

	sa = g_memdup (id->chosen_host, sizeof (id->chosen_host));

	header.version = XDM_PROTOCOL_VERSION;
	header.opcode = (CARD16) FORWARD_QUERY;
	header.length = 0;
	header.length += 2 + address.length;
	header.length += 2 + port.length;
	header.length += 1;
	for (i = 0; i < authlist->length; i++) {
		header.length += 2 + authlist->data[i].length;
	}

	XdmcpWriteHeader (&manager->priv->buf, &header);
	XdmcpWriteARRAY8 (&manager->priv->buf, &address);
	XdmcpWriteARRAY8 (&manager->priv->buf, &port);
	XdmcpWriteARRAYofARRAY8 (&manager->priv->buf, authlist);

	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr) sa,
		    (int)sizeof (struct sockaddr_storage));

	g_free (port.data);
	g_free (address.data);
	g_free (sa);
}

static void
handle_any_query (GdmXdmcpManager         *manager,
		  struct sockaddr_storage *clnt_sa,
		  ARRAYofARRAY8Ptr         authentication_names,
		  int                      type)
{
	gdm_xdmcp_send_willing (manager, clnt_sa);
}

static void
handle_direct_query (GdmXdmcpManager         *manager,
		     struct sockaddr_storage *clnt_sa,
		     int                      len,
		     int                      type)
{
	ARRAYofARRAY8 clnt_authlist;
	int           expected_len;
	int           i;
	int           res;

	res = XdmcpReadARRAYofARRAY8 (&manager->priv->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 (manager, clnt_sa, &clnt_authlist, type);
	} else {
		g_warning (_("Error in checksum"));
	}

	XdmcpDisposeARRAYofARRAY8 (&clnt_authlist);
}

static void
gdm_xdmcp_handle_broadcast_query (GdmXdmcpManager         *manager,
				  struct sockaddr_storage *clnt_sa,
				  int                      len)
{
	if (gdm_xdmcp_host_allow (clnt_sa)) {
		handle_direct_query (manager, clnt_sa, len, BROADCAST_QUERY);
	} else {
		/* just ignore it */
	}
}

static void
gdm_xdmcp_handle_query (GdmXdmcpManager         *manager,
			struct sockaddr_storage *clnt_sa,
			int                      len)
{
	if (gdm_xdmcp_host_allow (clnt_sa)) {
		handle_direct_query (manager, clnt_sa, len, QUERY);
	} else {
		gdm_xdmcp_send_unwilling (manager, clnt_sa, QUERY);
	}
}

static void
gdm_xdmcp_handle_indirect_query (GdmXdmcpManager         *manager,
				 struct sockaddr_storage *clnt_sa,
				 int                      len)
{
	ARRAYofARRAY8 clnt_authlist;
	int           expected_len;
	int           i;
	int           res;

	if (! gdm_xdmcp_host_allow (clnt_sa)) {
		/* ignore the request */
		return;
	}

	if (! manager->priv->honor_indirect) {
		/* ignore it */
		return;
	}

	res = XdmcpReadARRAYofARRAY8 (&manager->priv->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) {
		GdmIndirectDisplay *id;

		id = gdm_choose_indirect_lookup (clnt_sa);

		if (id != NULL && id->chosen_host != NULL) {
			/* if user chose us, then just send willing */
			if (gdm_address_is_local (id->chosen_host)) {
				/* get rid of indirect, so that we don't get
				 * the chooser */
				gdm_choose_indirect_dispose (id);
				gdm_xdmcp_send_willing (manager, clnt_sa);
			} else if (gdm_address_is_loopback (clnt_sa)) {
				/* 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 ();

				while (list != NULL) {
					struct sockaddr_storage *saddr = list->data;

					if (! gdm_address_is_loopback (saddr)) {
						/* forward query to * chosen host */
						gdm_xdmcp_send_forward_query (manager,
									      id,
									      clnt_sa,
									      saddr,
									      &clnt_authlist);
					}

					list = list->next;
				}
			} else {
				/* or send forward query to chosen host */
				gdm_xdmcp_send_forward_query (manager,
							      id,
							      clnt_sa,
							      clnt_sa,
							      &clnt_authlist);
			}
		} else if (id == NULL) {
			id = gdm_choose_indirect_alloc (clnt_sa);
			if (id != NULL) {
				gdm_xdmcp_send_willing (manager, clnt_sa);
			}
		} else  {
			gdm_xdmcp_send_willing (manager, clnt_sa);
		}

	} else {
		g_warning (_("Error in checksum"));
	}

	XdmcpDisposeARRAYofARRAY8 (&clnt_authlist);
}

static void
gdm_forward_query_dispose (GdmXdmcpManager *manager,
			   GdmForwardQuery *q)
{
	if (q == NULL) {
		return;
	}

	manager->priv->forward_queries = g_slist_remove (manager->priv->forward_queries, q);

	q->acctime = 0;

	{
		char *host;

		gdm_address_get_info (q->dsp_sa, &host, NULL);
		g_debug ("gdm_forward_query_dispose: Disposing %s", host);
		g_free (host);
	}

	g_free (q->dsp_sa);
	q->dsp_sa = NULL;
	g_free (q->from_sa);
	q->from_sa = NULL;

	g_free (q);
}

static gboolean
remove_oldest_forward (GdmXdmcpManager *manager)
{
	GSList          *li;
	GdmForwardQuery *oldest = NULL;

	for (li = manager->priv->forward_queries; li != NULL; li = li->next) {
		GdmForwardQuery *query = li->data;

		if (oldest == NULL || query->acctime < oldest->acctime) {
			oldest = query;
		}
	}

	if (oldest != NULL) {
		gdm_forward_query_dispose (manager, oldest);
		return TRUE;
	} else {
		return FALSE;
	}
}

static GdmForwardQuery *
gdm_forward_query_alloc (GdmXdmcpManager         *manager,
			 struct sockaddr_storage *mgr_sa,
			 struct sockaddr_storage *dsp_sa)
{
	GdmForwardQuery *q;
	int              count;

	count = g_slist_length (manager->priv->forward_queries);

	while (count > GDM_MAX_FORWARD_QUERIES && remove_oldest_forward (manager)) {
		count--;
	}

	q = g_new0 (GdmForwardQuery, 1);
	q->dsp_sa = g_memdup (dsp_sa, sizeof (struct sockaddr_storage));
	q->from_sa = g_memdup (mgr_sa, sizeof (struct sockaddr_storage));

	manager->priv->forward_queries = g_slist_prepend (manager->priv->forward_queries, q);

	return q;
}

static GdmForwardQuery *
gdm_forward_query_lookup (GdmXdmcpManager         *manager,
			  struct sockaddr_storage *clnt_sa)
{
	GSList          *li;
	GSList          *qlist;
	GdmForwardQuery *q;
	time_t           curtime;

	curtime = time (NULL);

	qlist = g_slist_copy (manager->priv->forward_queries);

	for (li = qlist; li != NULL; li = li->next) {
		q = (GdmForwardQuery *) li->data;

		if (q == NULL)
			continue;

		if (gdm_address_equal (q->dsp_sa, clnt_sa)) {
			g_slist_free (qlist);
			return q;
		}

		if (q->acctime > 0 &&  curtime > q->acctime + GDM_FORWARD_QUERY_TIMEOUT) {
			char *host;
			char *serv;

			gdm_address_get_info (q->dsp_sa, &host, &serv);

			g_debug ("gdm_forward_query_lookup: Disposing stale forward query from %s:%s",
				 host, serv);
			g_free (host);
			g_free (serv);

			gdm_forward_query_dispose (manager, q);
			continue;
		}
	}

	g_slist_free (qlist);

	{
		char *host;

		gdm_address_get_info (clnt_sa, &host, NULL);
		g_debug ("gdm_forward_query_lookup: Host %s not found",
			 host);
		g_free (host);
	}

	return NULL;
}

static gboolean
create_sa_from_request (ARRAY8 *req_addr,
			ARRAY8 *req_port,
			int    family,
			struct sockaddr_storage **sap)
{
	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 (sap != NULL) {
		*sap = 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;
	hints.ai_flags = AI_V4MAPPED; /* this should convert IPv4 address to IPv6 if needed */
	if ((gaierr = getaddrinfo (host, serv, &hints, &ai_list)) != 0) {
		g_warning ("Unable get address: %s", gai_strerror (gaierr));
		return FALSE;
	}

	/* just take the first one */
	ai = ai_list;

	found = FALSE;
	if (ai != NULL) {
		found = TRUE;
		if (sap != NULL) {
			*sap = g_memdup (ai->ai_addr, ai->ai_addrlen);
		}
	}

	freeaddrinfo (ai_list);

	return found;
}

static void
gdm_xdmcp_whack_queued_managed_forwards (GdmXdmcpManager         *manager,
					 struct sockaddr_storage *clnt_sa,
					 struct sockaddr_storage *origin)
{
	GSList *li;

	for (li = manager->priv->managed_forwards; li != NULL; li = li->next) {
		ManagedForward *mf = li->data;

		if (gdm_address_equal (&mf->manager, clnt_sa) &&
		    gdm_address_equal (&mf->origin, origin)) {
			manager->priv->managed_forwards = g_slist_remove_link (manager->priv->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 (GdmXdmcpManager         *manager,
				struct sockaddr_storage *clnt_sa,
				int                      len)
{
	ARRAY8                   clnt_addr;
	ARRAY8                   clnt_port;
	ARRAYofARRAY8            clnt_authlist;
	int                      i;
	int                      explen;
	struct sockaddr_storage *disp_sa;
	char                    *host;
	char                    *serv;

	disp_sa = NULL;

	/* Check with tcp_wrappers if client is allowed to access */
	if (! gdm_xdmcp_host_allow (clnt_sa)) {
		char *host;

		gdm_address_get_info (clnt_sa, &host, NULL);

		g_warning ("%s: Got FORWARD_QUERY from banned host %s",
			   "gdm_xdmcp_handle_forward query",
			   host);
		g_free (host);
		return;
	}

	/* Read display address */
	if G_UNLIKELY (! XdmcpReadARRAY8 (&manager->priv->buf, &clnt_addr)) {
		g_warning (_("%s: Could not read display address"),
			   "gdm_xdmcp_handle_forward_query");
		return;
	}

	/* Read display port */
	if G_UNLIKELY (! XdmcpReadARRAY8 (&manager->priv->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 (&manager->priv->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 ("gdm_xdmcp_handle_forward_query: 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_sa_from_request (&clnt_addr, &clnt_port, clnt_sa->ss_family, &disp_sa)) {
		g_warning ("Unable to parse address for request");
		goto out;
	}

	gdm_xdmcp_whack_queued_managed_forwards (manager,
						 clnt_sa,
						 disp_sa);

	gdm_address_get_info (disp_sa, &host, &serv);
	g_debug ("gdm_xdmcp_handle_forward_query: Got FORWARD_QUERY for display: %s, port %s",
		 host, serv);
	g_free (host);
	g_free (serv);

	/* Check with tcp_wrappers if display is allowed to access */
	if (gdm_xdmcp_host_allow (disp_sa)) {
		GdmForwardQuery *q;

		q = gdm_forward_query_lookup (manager, disp_sa);
		if (q != NULL)
			gdm_forward_query_dispose (manager, q);

		gdm_forward_query_alloc (manager, clnt_sa, disp_sa);

		gdm_xdmcp_send_willing (manager, disp_sa);
	}

 out:

	g_free (disp_sa);
	XdmcpDisposeARRAYofARRAY8 (&clnt_authlist);
	XdmcpDisposeARRAY8 (&clnt_port);
	XdmcpDisposeARRAY8 (&clnt_addr);
}

static void
gdm_xdmcp_really_send_managed_forward (GdmXdmcpManager         *manager,
				       struct sockaddr_storage *clnt_sa,
				       struct sockaddr_storage *origin)
{
	ARRAY8      address;
	XdmcpHeader header;
	char       *host;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("XDMCP: Sending MANAGED_FORWARD to %s", host);
	g_free (host);

	set_address_for_request (origin, &address);

	header.opcode = (CARD16) GDM_XDMCP_MANAGED_FORWARD;
	header.length = 4 + address.length;
	header.version = GDM_XDMCP_PROTOCOL_VERSION;
	XdmcpWriteHeader (&manager->priv->buf, &header);

	XdmcpWriteARRAY8 (&manager->priv->buf, &address);
	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));

	g_free (address.data);
}

static gboolean
managed_forward_handler (ManagedForward *mf)
{
	if (mf->xdmcp_manager->priv->socket_fd > 0) {
		gdm_xdmcp_really_send_managed_forward (mf->xdmcp_manager,
						       &(mf->manager),
						       &(mf->origin));
	}

	mf->times++;
	if (mf->xdmcp_manager->priv->socket_fd <= 0 || mf->times >= 2) {
		mf->xdmcp_manager->priv->managed_forwards = g_slist_remove (mf->xdmcp_manager->priv->managed_forwards, mf);
		mf->handler = 0;
		/* mf freed by glib */
		return FALSE;
	}
	return TRUE;
}

static void
gdm_xdmcp_send_managed_forward (GdmXdmcpManager         *manager,
				struct sockaddr_storage *clnt_sa,
				struct sockaddr_storage *origin)
{
	ManagedForward *mf;

	gdm_xdmcp_really_send_managed_forward (manager, clnt_sa, origin);

	mf = g_new0 (ManagedForward, 1);
	mf->times = 0;
	mf->xdmcp_manager = manager;

	memcpy (&(mf->manager), clnt_sa, sizeof (struct sockaddr_storage));
	memcpy (&(mf->origin), origin, sizeof (struct sockaddr_storage));

	mf->handler = g_timeout_add_full (G_PRIORITY_DEFAULT,
					  MANAGED_FORWARD_INTERVAL,
					  (GSourceFunc)managed_forward_handler,
					  mf,
					  (GDestroyNotify) g_free);
	manager->priv->managed_forwards = g_slist_prepend (manager->priv->managed_forwards, mf);
}

static void
gdm_xdmcp_send_got_managed_forward (GdmXdmcpManager         *manager,
				    struct sockaddr_storage *clnt_sa,
				    struct sockaddr_storage *origin)
{
	ARRAY8      address;
	XdmcpHeader header;
	char       *host;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("XDMCP: Sending GOT_MANAGED_FORWARD to %s", host);
	g_free (host);

	set_address_for_request (origin, &address);

	header.opcode = (CARD16) GDM_XDMCP_GOT_MANAGED_FORWARD;
	header.length = 4 + address.length;
	header.version = GDM_XDMCP_PROTOCOL_VERSION;
	XdmcpWriteHeader (&manager->priv->buf, &header);

	XdmcpWriteARRAY8 (&manager->priv->buf, &address);
	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));
}

static void
gdm_xdmcp_recount_sessions (GdmXdmcpManager *manager)
{
       GSList *li;
       GSList *displays;

       displays = gdm_daemon_config_get_display_list ();

       manager->priv->num_sessions = 0;
       manager->priv->num_pending_sessions = 0;

       for (li = displays; li != NULL; li = li->next) {
               GdmDisplay *d = li->data;
	       if (SERVER_IS_XDMCP (d)) {
		       if (d->dispstat == XDMCP_MANAGED)
			       manager->priv->num_sessions++;
		       else if (d->dispstat == XDMCP_PENDING)
			       manager->priv->num_pending_sessions++;
	       }
       }
}

static void
do_dispose (GdmXdmcpManager *manager,
	    GdmDisplay      *d)
{

	gdm_display_dispose (d);
	gdm_xdmcp_recount_sessions (manager);
}

static void
gdm_xdmcp_displays_purge (GdmXdmcpManager *manager)
{
	GSList *dlist;
	time_t curtime = time (NULL);
	GSList *displays;

	displays = gdm_daemon_config_get_display_list ();

	dlist = displays;
	while (dlist != NULL) {
		GdmDisplay *d = dlist->data;

		if (d != NULL &&
		    SERVER_IS_XDMCP (d) &&
		    d->dispstat == XDMCP_PENDING &&
		    curtime > d->acctime + manager->priv->max_wait) {
			g_debug ("gdm_xdmcp_displays_purge: Disposing session id %ld",
				 (long)d->sessionid);
			do_dispose (manager, d);

			/* restart as the list is now broken */
			dlist = displays;
		} else {
			/* just go on */
			dlist = dlist->next;
		}
	}
}

static void
gdm_xdmcp_display_dispose_check (GdmXdmcpManager *manager,
				 const char      *hostname,
				 int              dspnum)
{
	GSList *dlist;
	GSList *displays;

	if (hostname == NULL) {
		return;
	}

	g_debug ("gdm_xdmcp_display_dispose_check (%s:%d)", hostname, dspnum);

	displays = gdm_daemon_config_get_display_list ();

	dlist = displays;
	while (dlist != NULL) {
		GdmDisplay *d = dlist->data;

		if (d != NULL &&
		    SERVER_IS_XDMCP (d) &&
		    d->xdmcp_dispnum == dspnum &&
		    strcmp (d->hostname, hostname) == 0) {

			if (d->dispstat == XDMCP_MANAGED) {
				gdm_display_unmanage (d);
			} else {
				do_dispose (manager, d);
			}

			/* restart as the list is now broken */
			dlist = displays;
		} else {
			/* just go on */
			dlist = dlist->next;
		}
	}
}

static void
gdm_xdmcp_send_decline (GdmXdmcpManager         *manager,
			struct sockaddr_storage *clnt_sa,
			const char              *reason)
{
	XdmcpHeader      header;
	ARRAY8           authentype;
	ARRAY8           authendata;
	ARRAY8           status;
	GdmForwardQuery *fq;
	char            *host;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("XDMCP: Sending DECLINE to %s", host);
	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 (&manager->priv->buf, &header);
	XdmcpWriteARRAY8 (&manager->priv->buf, &status);
	XdmcpWriteARRAY8 (&manager->priv->buf, &authentype);
	XdmcpWriteARRAY8 (&manager->priv->buf, &authendata);

	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));

	/* Send MANAGED_FORWARD to indicate that the connection
	 * reached some sort of resolution */
	fq = gdm_forward_query_lookup (manager, clnt_sa);
	if (fq != NULL) {
		gdm_xdmcp_send_managed_forward (manager, fq->from_sa, clnt_sa);
		gdm_forward_query_dispose (manager, fq);
	}
}

static GdmDisplay *
gdm_xdmcp_display_alloc (GdmXdmcpManager         *manager,
			 struct sockaddr_storage *addr,
			 GdmHostent              *he /* eaten and freed */,
			 int                      displaynum)
{
	GdmDisplay *d  = NULL;
	const char *proxycmd;
	gboolean    use_proxy = FALSE;

	proxycmd = NULL;
	use_proxy = FALSE;
#if 0
	proxycmd = gdm_daemon_config_get_value_string (GDM_KEY_XDMCP_PROXY_XSERVER);
	use_proxy = FALSE;
#endif
	d = g_new0 (GdmDisplay, 1);

	if (use_proxy && proxycmd != NULL) {
		d->type = TYPE_XDMCP_PROXY;
		d->command = g_strdup (proxycmd);
		g_debug ("Using proxy server for XDMCP: %s\n", d->command);
	} else {
		d->type = TYPE_XDMCP;
	}

	d->logout_action    = GDM_LOGOUT_ACTION_NONE;
	d->authfile         = NULL;
	d->auths            = NULL;
	d->userauth         = NULL;
	d->greetpid         = 0;
	d->servpid          = 0;
	d->servstat         = 0;
	d->sesspid          = 0;
	d->slavepid         = 0;
	d->attached         = FALSE;
	d->dispstat         = XDMCP_PENDING;
	d->sessionid        = get_next_session_serial (manager);

	d->acctime          = time (NULL);
	d->dispnum          = displaynum;
	d->xdmcp_dispnum    = displaynum;

	d->handled          = TRUE;
	d->tcp_disallowed   = FALSE;
	d->vt               = -1;
	d->vtnum            = -1;
	d->x_servers_order  = -1;
	d->logged_in        = FALSE;
	d->login            = NULL;
	d->sleep_before_run = 0;

	if (gdm_daemon_config_get_value_bool (GDM_KEY_ALLOW_REMOTE_AUTOLOGIN) &&
	    ! ve_string_empty (gdm_daemon_config_get_value_string (GDM_KEY_TIMED_LOGIN))) {
		d->timed_login_ok = TRUE;
	} else {
		d->timed_login_ok = FALSE;
	}

	d->name = g_strdup_printf ("%s:%d",
				   he->hostname,
				   displaynum);

	memcpy (&d->addr, addr, sizeof (struct sockaddr_storage));

	d->hostname              = he->hostname;
	he->hostname             = NULL;
	d->addrs                 = he->addrs;
	he->addrs                = NULL;
	d->addr_count            = he->addr_count;
	he->addr_count           = 0;

	gdm_hostent_free (he);

	d->windowpath            = NULL;
	d->slave_notify_fd       = -1;
	d->master_notify_fd      = -1;
	d->xsession_errors_bytes = 0;
	d->xsession_errors_fd    = -1;
	d->session_output_fd     = -1;
	d->chooser_output_fd     = -1;
	d->chooser_last_line     = NULL;
	d->theme_name            = NULL;

	/* Secure display with cookie */
	if G_UNLIKELY (! gdm_auth_secure_display (d)) {
		g_warning ("gdm_xdmcp_display_alloc: Error setting up cookies for %s",
			   d->name);
	}

	if (d->type == TYPE_XDMCP_PROXY) {
		d->parent_disp      = d->name;
		d->name             = g_strdup (":-1");
		d->dispnum          = -1;
		d->server_uid       = gdm_daemon_config_get_gdmuid ();
		d->parent_auth_file = d->authfile;
		d->authfile         = NULL;
	}

	gdm_daemon_config_display_list_append (d);

	manager->priv->num_pending_sessions++;

	g_debug ("gdm_xdmcp_display_alloc: display=%s, session id=%ld, xdmcp_pending=%d",
		   d->name, (long)d->sessionid, manager->priv->num_pending_sessions);

	return d;
}

static void
gdm_xdmcp_send_accept (GdmXdmcpManager         *manager,
		       struct sockaddr_storage *clnt_sa,
		       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 (&manager->priv->buf, &header);
	XdmcpWriteCARD32 (&manager->priv->buf, session_id);
	XdmcpWriteARRAY8 (&manager->priv->buf, authentication_name);
	XdmcpWriteARRAY8 (&manager->priv->buf, authentication_data);
	XdmcpWriteARRAY8 (&manager->priv->buf, authorization_name);
	XdmcpWriteARRAY8 (&manager->priv->buf, authorization_data);

	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("XDMCP: Sending ACCEPT to %s with SessionID=%ld",
		 host,
		 (long)session_id);
	g_free (host);
}

static void
gdm_xdmcp_handle_request (GdmXdmcpManager         *manager,
			  struct sockaddr_storage *clnt_sa,
			  int                      len)
{
	CARD16        clnt_dspnum;
	ARRAY16       clnt_conntyp;
	ARRAYofARRAY8 clnt_addr;
	ARRAY8        clnt_authname;
	ARRAY8        clnt_authdata;
	ARRAYofARRAY8 clnt_authorization;
	ARRAY8        clnt_manufacturer;
	int           explen;
	int           i;
	gboolean      mitauth;
	gboolean      entered;
	char         *host;

	mitauth = FALSE;
	entered = FALSE;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("gdm_xdmcp_handle_request: Got REQUEST from %s", host);

	/* Check with tcp_wrappers if client is allowed to access */
	if (! gdm_xdmcp_host_allow (clnt_sa)) {
		g_warning (_("%s: Got REQUEST from banned host %s"),
			   "gdm_xdmcp_handle_request",
			   host);
		g_free (host);
		return;
	}
	g_free (host);

	gdm_xdmcp_displays_purge (manager); /* Purge pending displays */

	/* Remote display number */
	if G_UNLIKELY (! XdmcpReadCARD16 (&manager->priv->buf, &clnt_dspnum)) {
		g_warning (_("%s: Could not read Display Number"),
			   "gdm_xdmcp_handle_request");
		return;
	}

	/* We don't care about connection type. Address says it all */
	if G_UNLIKELY (! XdmcpReadARRAY16 (&manager->priv->buf, &clnt_conntyp)) {
		g_warning (_("%s: Could not read Connection Type"),
			   "gdm_xdmcp_handle_request");
		return;
	}

	/* This is TCP/IP - we don't care */
	if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&manager->priv->buf, &clnt_addr)) {
		g_warning (_("%s: Could not read Client Address"),
			   "gdm_xdmcp_handle_request");
		XdmcpDisposeARRAY16 (&clnt_conntyp);
		return;
	}

	/* Read authentication type */
	if G_UNLIKELY (! XdmcpReadARRAY8 (&manager->priv->buf, &clnt_authname)) {
		g_warning (_("%s: Could not read Authentication Names"),
			   "gdm_xdmcp_handle_request");
		XdmcpDisposeARRAYofARRAY8 (&clnt_addr);
		XdmcpDisposeARRAY16 (&clnt_conntyp);
		return;
	}

	/* Read authentication data */
	if G_UNLIKELY (! XdmcpReadARRAY8 (&manager->priv->buf, &clnt_authdata)) {
		g_warning (_("%s: Could not read Authentication Data"),
			   "gdm_xdmcp_handle_request");
		XdmcpDisposeARRAYofARRAY8 (&clnt_addr);
		XdmcpDisposeARRAY16 (&clnt_conntyp);
		XdmcpDisposeARRAY8 (&clnt_authname);
		return;
	}

	/* Read and select from supported authorization list */
	if G_UNLIKELY (! XdmcpReadARRAYofARRAY8 (&manager->priv->buf, &clnt_authorization)) {
		g_warning (_("%s: Could not read Authorization List"),
			   "gdm_xdmcp_handle_request");
		XdmcpDisposeARRAY8 (&clnt_authdata);
		XdmcpDisposeARRAYofARRAY8 (&clnt_addr);
		XdmcpDisposeARRAY16 (&clnt_conntyp);
		XdmcpDisposeARRAY8 (&clnt_authname);
		return;
	}

	/* libXdmcp doesn't terminate strings properly so we cheat and use strncmp () */
	for (i = 0 ; i < clnt_authorization.length ; i++)
		if (clnt_authorization.data[i].length == 18 &&
		    strncmp ((char *) clnt_authorization.data[i].data,
			     "MIT-MAGIC-COOKIE-1", 18) == 0)
			mitauth = TRUE;

	/* Manufacturer ID */
	if G_UNLIKELY (! XdmcpReadARRAY8 (&manager->priv->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);
		XdmcpDisposeARRAY16 (&clnt_conntyp);
		return;
	}

	/* 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.length ; i++)
		explen += 2 + clnt_authorization.data[i].length;
	explen += 2 + clnt_manufacturer.length;

	if G_UNLIKELY (explen != len) {
		gdm_address_get_info (clnt_sa, &host, NULL);
		g_warning (_("%s: Failed checksum from %s"),
			   "gdm_xdmcp_handle_request",
			   host);
		g_free (host);

		XdmcpDisposeARRAY8 (&clnt_authname);
		XdmcpDisposeARRAY8 (&clnt_authdata);
		XdmcpDisposeARRAY8 (&clnt_manufacturer);
		XdmcpDisposeARRAYofARRAY8 (&clnt_addr);
		XdmcpDisposeARRAYofARRAY8 (&clnt_authorization);
		XdmcpDisposeARRAY16 (&clnt_conntyp);
		return;
	}

	{
		char *s = g_strndup ((char *) clnt_manufacturer.data, clnt_manufacturer.length);
		g_debug ("gdm_xdmcp_handle_request: xdmcp_pending=%d, MaxPending=%d, xdmcp_sessions=%d, MaxSessions=%d, ManufacturerID=%s",
			 manager->priv->num_pending_sessions,
			 manager->priv->max_pending_displays,
			 manager->priv->num_sessions,
			 manager->priv->max_displays,
			 ve_sure_string (s));
		g_free (s);
	}

	/* Check if ok to manage display */
	if (mitauth &&
	    manager->priv->num_sessions < manager->priv->max_displays &&
	    (gdm_address_is_local (clnt_sa) ||
	     gdm_xdmcp_num_displays_from_host (manager, clnt_sa) < manager->priv->max_displays_per_host)) {
		entered = TRUE;
	}

	if (entered) {
		GdmHostent *he;
		he = gdm_gethostbyaddr (clnt_sa);

		/* Check if we are already talking to this host */
		gdm_xdmcp_display_dispose_check (manager, he->hostname, clnt_dspnum);

		if (manager->priv->num_pending_sessions >= manager->priv->max_pending_displays) {
			g_debug ("gdm_xdmcp_handle_request: 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 (manager, clnt_sa, "Maximum pending servers");
			gdm_hostent_free (he);
		} else {
			GdmDisplay *d;

			d = gdm_xdmcp_display_alloc (manager,
						     clnt_sa,
						     he /* eaten and freed */,
						     clnt_dspnum);
			if (d != NULL) {
				ARRAY8 authentication_name;
				ARRAY8 authentication_data;
				ARRAY8 authorization_name;
				ARRAY8 authorization_data;

				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 *) d->bcookie;
				authorization_data.length   = 16;

				/* the addrs are NOT copied */
				gdm_xdmcp_send_accept (manager,
						       clnt_sa,
						       d->sessionid,
						       &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 (manager,
						clnt_sa,
						"Only MIT-MAGIC-COOKIE-1 supported");
		} else if (manager->priv->num_sessions >= manager->priv->max_displays) {
			g_warning ("Maximum number of open XDMCP sessions reached");
			gdm_xdmcp_send_decline (manager,
						clnt_sa,
						"Maximum number of open sessions reached");
		} else {
			g_debug ("Maximum number of open XDMCP sessions from host %s reached",
				 host);
			gdm_xdmcp_send_decline (manager,
						clnt_sa,
						"Maximum number of open sessions from your host reached");
		}
	}

	XdmcpDisposeARRAY8 (&clnt_authname);
	XdmcpDisposeARRAY8 (&clnt_authdata);
	XdmcpDisposeARRAY8 (&clnt_manufacturer);
	XdmcpDisposeARRAYofARRAY8 (&clnt_addr);
	XdmcpDisposeARRAYofARRAY8 (&clnt_authorization);
	XdmcpDisposeARRAY16 (&clnt_conntyp);
}

static GdmDisplay *
gdm_xdmcp_display_lookup (GdmXdmcpManager *manager,
			  CARD32           sessid)
{
	GSList     *l;
	GdmDisplay *d;
	GSList *displays;

	if (sessid == 0) {
		return (NULL);
	}

	displays = gdm_daemon_config_get_display_list ();

	l = displays;
	while (l != NULL) {
		d = (GdmDisplay *) l->data;

		if (d && d->sessionid == sessid) {
			return (d);
		}

		l = l->next;
	}

	return (NULL);
}

static void
gdm_xdmcp_send_failed (GdmXdmcpManager         *manager,
		       struct sockaddr_storage *clnt_sa,
		       CARD32                   sessid)
{
	XdmcpHeader header;
	ARRAY8      status;

	g_debug ("XDMCP: 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 (&manager->priv->buf, &header);
	XdmcpWriteCARD32 (&manager->priv->buf, sessid);
	XdmcpWriteARRAY8 (&manager->priv->buf, &status);

	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));
}

static void
gdm_xdmcp_send_refuse (GdmXdmcpManager         *manager,
		       struct sockaddr_storage *clnt_sa,
		       CARD32                   sessid)
{
	XdmcpHeader      header;
	GdmForwardQuery *fq;

	g_debug ("XDMCP: Sending REFUSE to %ld",
		 (long)sessid);

	header.version = XDM_PROTOCOL_VERSION;
	header.opcode  = (CARD16) REFUSE;
	header.length  = 4;

	XdmcpWriteHeader (&manager->priv->buf, &header);
	XdmcpWriteCARD32 (&manager->priv->buf, sessid);

	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));

	/*
	 * This was from a forwarded query quite apparently so
	 * send MANAGED_FORWARD
	 */
	fq = gdm_forward_query_lookup (manager, clnt_sa);
	if (fq != NULL) {
		gdm_xdmcp_send_managed_forward (manager, fq->from_sa, clnt_sa);
		gdm_forward_query_dispose (manager, fq);
	}
}

static void
gdm_xdmcp_handle_manage (GdmXdmcpManager         *manager,
			 struct sockaddr_storage *clnt_sa,
			 int                      len)
{
	CARD32              clnt_sessid;
	CARD16              clnt_dspnum;
	ARRAY8              clnt_dspclass;
	GdmDisplay         *d;
	GdmIndirectDisplay *id;
	GdmForwardQuery    *fq;
	char               *host;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("gdm_xdmcp_handle_manage: Got MANAGE from %s", host);

	/* Check with tcp_wrappers if client is allowed to access */
	if (! gdm_xdmcp_host_allow (clnt_sa)) {
		g_warning (_("%s: Got Manage from banned host %s"),
			   "gdm_xdmcp_handle_manage",
			   host);
		g_free (host);
		return;
	}
	g_free (host);

	/* SessionID */
	if G_UNLIKELY (! XdmcpReadCARD32 (&manager->priv->buf, &clnt_sessid)) {
		g_warning (_("%s: Could not read Session ID"),
			   "gdm_xdmcp_handle_manage");
		return;
	}

	/* Remote display number */
	if G_UNLIKELY (! XdmcpReadCARD16 (&manager->priv->buf, &clnt_dspnum)) {
		g_warning (_("%s: Could not read Display Number"),
			   "gdm_xdmcp_handle_manage");
		return;
	}

	/* Display Class */
	if G_UNLIKELY (! XdmcpReadARRAY8 (&manager->priv->buf, &clnt_dspclass)) {
		g_warning (_("%s: Could not read Display Class"),
			   "gdm_xdmcp_handle_manage");
		return;
	}

	if G_UNLIKELY (gdm_daemon_config_get_value_bool (GDM_KEY_DEBUG)) {
		char *s = g_strndup ((char *) clnt_dspclass.data, clnt_dspclass.length);
		g_debug ("gdm_xdmcp-handle_manage: Got display=%d, SessionID=%ld Class=%s from %s",
			   (int)clnt_dspnum, (long)clnt_sessid, ve_sure_string (s), host);

		g_free (s);
	}

	d = gdm_xdmcp_display_lookup (manager, clnt_sessid);
	if (d != NULL &&
	    d->dispstat == XDMCP_PENDING) {

		g_debug ("gdm_xdmcp_handle_manage: Looked up %s", d->name);

		if (gdm_daemon_config_get_value_bool (GDM_KEY_INDIRECT)) {
			id = gdm_choose_indirect_lookup (clnt_sa);

			/* This was an indirect thingie and nothing was yet chosen,
			 * use a chooser */
			if (d->dispstat == XDMCP_PENDING &&
			    id != NULL &&
			    id->chosen_host == NULL) {
				d->use_chooser = TRUE;
				d->indirect_id = id->id;
			} else {
				d->indirect_id = 0;
				d->use_chooser = FALSE;
				if (id != NULL)
					gdm_choose_indirect_dispose (id);
			}
		} else {
			d->indirect_id = 0;
			d->use_chooser = FALSE;
		}

		/* this was from a forwarded query quite apparently so
		 * send MANAGED_FORWARD */
		fq = gdm_forward_query_lookup (manager, clnt_sa);
		if (fq != NULL) {
			gdm_xdmcp_send_managed_forward (manager, fq->from_sa, clnt_sa);
			gdm_forward_query_dispose (manager, fq);
		}

		d->dispstat = XDMCP_MANAGED;
		manager->priv->num_sessions++;
		manager->priv->num_pending_sessions--;

		/* Start greeter/session */
		if G_UNLIKELY (!gdm_display_manage (d)) {
			gdm_xdmcp_send_failed (manager, clnt_sa, clnt_sessid);
			XdmcpDisposeARRAY8 (&clnt_dspclass);
			return;
		}
	} else if G_UNLIKELY (d != NULL && d->dispstat == XDMCP_MANAGED) {
		g_debug ("gdm_xdmcp_handle_manage: Session id %ld already managed",
			   (long)clnt_sessid);
	} else {
		g_warning ("gdm_xdmcp_handle_manage: Failed to look up session id %ld",
			   (long)clnt_sessid);
		gdm_xdmcp_send_refuse (manager, clnt_sa, clnt_sessid);
	}

	XdmcpDisposeARRAY8 (&clnt_dspclass);
}

static void
gdm_xdmcp_handle_managed_forward (GdmXdmcpManager         *manager,
				  struct sockaddr_storage *clnt_sa,
				  int                      len)
{
	ARRAY8 clnt_address;
	GdmIndirectDisplay *id;
	char *host;
	struct sockaddr_storage *disp_sa;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("gdm_xdmcp_handle_managed_forward: Got MANAGED_FORWARD from %s",
		   host);

	/* Check with tcp_wrappers if client is allowed to access */
	if (! gdm_xdmcp_host_allow (clnt_sa)) {
		g_warning ("%s: Got MANAGED_FORWARD from banned host %s",
			   "gdm_xdmcp_handle_request", host);
		g_free (host);
		return;
	}
	g_free (host);

	/* Hostname */
	if G_UNLIKELY ( ! XdmcpReadARRAY8 (&manager->priv->buf, &clnt_address)) {
		g_warning (_("%s: Could not read address"),
			   "gdm_xdmcp_handle_managed_forward");
		return;
	}

	disp_sa = NULL;
	if (! create_sa_from_request (&clnt_address, NULL, clnt_sa->ss_family, &disp_sa)) {
		g_warning ("Unable to parse address for request");
		XdmcpDisposeARRAY8 (&clnt_address);
		return;
	}

	id = gdm_choose_indirect_lookup_by_chosen (clnt_sa, disp_sa);
	if (id != NULL) {
		gdm_choose_indirect_dispose (id);
	}

	/* 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 (manager, clnt_sa, disp_sa);

	XdmcpDisposeARRAY8 (&clnt_address);
}

static void
gdm_xdmcp_handle_got_managed_forward (GdmXdmcpManager         *manager,
				      struct sockaddr_storage *clnt_sa,
				      int                      len)
{
	struct sockaddr_storage *disp_sa;
	ARRAY8 clnt_address;
	char  *host;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("gdm_xdmcp_handle_got_managed_forward: Got MANAGED_FORWARD from %s",
		   host);

	if (! gdm_xdmcp_host_allow (clnt_sa)) {
		g_warning ("%s: Got GOT_MANAGED_FORWARD from banned host %s",
			   "gdm_xdmcp_handle_request", host);
		g_free (host);
		return;
	}
	g_free (host);

	/* Hostname */
	if G_UNLIKELY ( ! XdmcpReadARRAY8 (&manager->priv->buf, &clnt_address)) {
		g_warning (_("%s: Could not read address"),
			   "gdm_xdmcp_handle_got_managed_forward");
		return;
	}

	if (! create_sa_from_request (&clnt_address, NULL, clnt_sa->ss_family, &disp_sa)) {
		g_warning (_("%s: Could not read address"),
			   "gdm_xdmcp_handle_got_managed_forward");
		XdmcpDisposeARRAY8 (&clnt_address);
		return;
	}

	gdm_xdmcp_whack_queued_managed_forwards (manager, clnt_sa, disp_sa);

	XdmcpDisposeARRAY8 (&clnt_address);
}

static void
gdm_xdmcp_send_alive (GdmXdmcpManager         *manager,
		      struct sockaddr_storage *clnt_sa,
                      CARD16                   dspnum,
		      CARD32                   sessid)
{
	XdmcpHeader header;
	GdmDisplay *d;
	int send_running = 0;
	CARD32 send_sessid = 0;

	d = gdm_xdmcp_display_lookup (manager, sessid);
	if (d == NULL) {
		d = gdm_xdmcp_display_lookup_by_host (manager, clnt_sa, dspnum);
	}

	if (d != NULL) {
		send_sessid = d->sessionid;
		if (d->dispstat == XDMCP_MANAGED) {
			send_running = 1;
		}
	}

	g_debug ("XDMCP: 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 (&manager->priv->buf, &header);
	XdmcpWriteCARD8 (&manager->priv->buf, send_running);
	XdmcpWriteCARD32 (&manager->priv->buf, send_sessid);

	XdmcpFlush (manager->priv->socket_fd,
		    &manager->priv->buf,
		    (XdmcpNetaddr)clnt_sa,
		    (int)sizeof (struct sockaddr_storage));
}

static void
gdm_xdmcp_handle_keepalive (GdmXdmcpManager         *manager,
			    struct sockaddr_storage *clnt_sa,
			    int                      len)
{
	CARD16 clnt_dspnum;
	CARD32 clnt_sessid;
	char *host;

	gdm_address_get_info (clnt_sa, &host, NULL);
	g_debug ("XDMCP: Got KEEPALIVE from %s", host);

	/* Check with tcp_wrappers if client is allowed to access */
	if (! gdm_xdmcp_host_allow (clnt_sa)) {
		g_warning (_("%s: Got KEEPALIVE from banned host %s"),
			   "gdm_xdmcp_handle_keepalive",
			   host);
		g_free (host);
		return;
	}
	g_free (host);

	/* Remote display number */
	if G_UNLIKELY (! XdmcpReadCARD16 (&manager->priv->buf, &clnt_dspnum)) {
		g_warning (_("%s: Could not read Display Number"),
			   "gdm_xdmcp_handle_keepalive");
		return;
	}

	/* SessionID */
	if G_UNLIKELY (! XdmcpReadCARD32 (&manager->priv->buf, &clnt_sessid)) {
		g_warning (_("%s: Could not read Session ID"),
			   "gdm_xdmcp_handle_keepalive");
		return;
	}

	gdm_xdmcp_send_alive (manager, clnt_sa, 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,
	       GdmXdmcpManager *manager)
{
	struct sockaddr_storage clnt_sa;
	gint                    sa_len;
	XdmcpHeader             header;
	char                   *host;
	char                   *port;
	int                     res;

	sa_len = sizeof (clnt_sa);

	g_debug ("decode_packet: GIOCondition %d", (int)cond);

	if ( ! (cond & G_IO_IN)) {
		return TRUE;
	}

	res = XdmcpFill (manager->priv->socket_fd, &manager->priv->buf, (XdmcpNetaddr)&clnt_sa, &sa_len);
	if G_UNLIKELY (! res) {
		g_debug (_("XDMCP: Could not create XDMCP buffer!"));
		return TRUE;
	}

	res = XdmcpReadHeader (&manager->priv->buf, &header);
	if G_UNLIKELY (! res) {
		g_warning (_("XDMCP: 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;
	}

	gdm_address_get_info (&clnt_sa, &host, &port);

	g_debug ("XDMCP: Received opcode %s from client %s : %s",
		 opcode_string (header.opcode),
		 host,
		 port);

	switch (header.opcode) {
	case BROADCAST_QUERY:
		gdm_xdmcp_handle_broadcast_query (manager, &clnt_sa, header.length);
		break;

	case QUERY:
		gdm_xdmcp_handle_query (manager, &clnt_sa, header.length);
		break;

	case INDIRECT_QUERY:
		gdm_xdmcp_handle_indirect_query (manager, &clnt_sa, header.length);
		break;

	case FORWARD_QUERY:
		gdm_xdmcp_handle_forward_query (manager, &clnt_sa, header.length);
		break;

	case REQUEST:
		gdm_xdmcp_handle_request (manager, &clnt_sa, header.length);
		break;

	case MANAGE:
		gdm_xdmcp_handle_manage (manager, &clnt_sa, header.length);
		break;

	case KEEPALIVE:
		gdm_xdmcp_handle_keepalive (manager, &clnt_sa, header.length);
		break;

	case GDM_XDMCP_MANAGED_FORWARD:
		gdm_xdmcp_handle_managed_forward (manager, &clnt_sa, header.length);
		break;

	case GDM_XDMCP_GOT_MANAGED_FORWARD:
		gdm_xdmcp_handle_got_managed_forward (manager, &clnt_sa, header.length);
		break;

	default:
		g_debug ("XDMCP: Unknown opcode from client %s : %s",
			 host,
			 port);

		break;
	}

	g_free (host);
	g_free (port);

	return TRUE;
}

gboolean
gdm_xdmcp_manager_start (GdmXdmcpManager *manager,
			 GError         **error)
{
	gboolean    ret;
	GIOChannel *ioc;

	g_return_val_if_fail (GDM_IS_XDMCP_MANAGER (manager), FALSE);
	g_return_val_if_fail (manager->priv->socket_fd == -1, FALSE);

	ret = open_port (manager);
	if (! ret) {
		return ret;
	}

	g_debug ("XDMCP: Starting to listen on XDMCP port");

	ioc = g_io_channel_unix_new (manager->priv->socket_fd);

	g_io_channel_set_encoding (ioc, NULL, NULL);
	g_io_channel_set_buffered (ioc, FALSE);

	manager->priv->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,
							      manager,
							      NULL);
	g_io_channel_unref (ioc);

	return ret;
}

gboolean
gdm_xdmcp_manager_stop (GdmXdmcpManager *manager,
			GError         **error)
{
	g_return_val_if_fail (GDM_IS_XDMCP_MANAGER (manager), FALSE);
	g_return_val_if_fail (manager->priv->socket_fd != -1, FALSE);

	if (manager->priv->socket_watch_id > 0) {
		g_source_remove (manager->priv->socket_watch_id);
		manager->priv->socket_watch_id = 0;
	}

	if (manager->priv->socket_fd > 0) {
		VE_IGNORE_EINTR (close (manager->priv->socket_fd));
		manager->priv->socket_fd = -1;
	}

	return TRUE;
}

void
gdm_xdmcp_manager_set_port (GdmXdmcpManager *manager,
			    guint            port)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	manager->priv->port = port;
}

static void
gdm_xdmcp_manager_set_use_multicast (GdmXdmcpManager *manager,
				     gboolean         use_multicast)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	manager->priv->use_multicast = use_multicast;
}

static void
gdm_xdmcp_manager_set_multicast_address (GdmXdmcpManager *manager,
					 const char      *address)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	g_free (manager->priv->multicast_address);
	manager->priv->multicast_address = g_strdup (address);
}

static void
gdm_xdmcp_manager_set_honor_indirect (GdmXdmcpManager *manager,
				      gboolean         honor_indirect)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	manager->priv->honor_indirect = honor_indirect;
}

static void
gdm_xdmcp_manager_set_max_displays_per_host (GdmXdmcpManager *manager,
					     guint            num)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	manager->priv->max_displays_per_host = num;
}

static void
gdm_xdmcp_manager_set_max_displays (GdmXdmcpManager *manager,
				    guint            num)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	manager->priv->max_displays = num;
}

static void
gdm_xdmcp_manager_set_max_pending_displays (GdmXdmcpManager *manager,
					    guint            num)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	manager->priv->max_pending_displays = num;
}

static void
gdm_xdmcp_manager_set_max_wait (GdmXdmcpManager *manager,
				guint            num)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	manager->priv->max_wait = num;
}

static void
gdm_xdmcp_manager_set_willing_script (GdmXdmcpManager *manager,
				      const char      *script)
{
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (manager));

	g_free (manager->priv->willing_script);
	manager->priv->willing_script = g_strdup (script);
}

static void
gdm_xdmcp_manager_set_property (GObject	      *object,
				guint	       prop_id,
				const GValue  *value,
				GParamSpec    *pspec)
{
	GdmXdmcpManager *self;

	self = GDM_XDMCP_MANAGER (object);

	switch (prop_id) {
	case PROP_PORT:
		gdm_xdmcp_manager_set_port (self, g_value_get_uint (value));
		break;
	case PROP_USE_MULTICAST:
		gdm_xdmcp_manager_set_use_multicast (self, g_value_get_boolean (value));
		break;
	case PROP_MULTICAST_ADDRESS:
		gdm_xdmcp_manager_set_multicast_address (self, g_value_get_string (value));
		break;
	case PROP_HONOR_INDIRECT:
		gdm_xdmcp_manager_set_honor_indirect (self, g_value_get_boolean (value));
		break;
	case PROP_MAX_DISPLAYS_PER_HOST:
		gdm_xdmcp_manager_set_max_displays_per_host (self, g_value_get_uint (value));
		break;
	case PROP_MAX_DISPLAYS:
		gdm_xdmcp_manager_set_max_displays (self, g_value_get_uint (value));
		break;
	case PROP_MAX_PENDING_DISPLAYS:
		gdm_xdmcp_manager_set_max_pending_displays (self, g_value_get_uint (value));
		break;
	case PROP_MAX_WAIT:
		gdm_xdmcp_manager_set_max_wait (self, g_value_get_uint (value));
		break;
	case PROP_WILLING_SCRIPT:
		gdm_xdmcp_manager_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_manager_get_property (GObject	   *object,
				guint  	    prop_id,
				GValue	   *value,
				GParamSpec *pspec)
{
	GdmXdmcpManager *self;

	self = GDM_XDMCP_MANAGER (object);

	switch (prop_id) {
	case PROP_PORT:
		g_value_set_uint (value, self->priv->port);
		break;
	case PROP_USE_MULTICAST:
		g_value_set_boolean (value, self->priv->use_multicast);
		break;
	case PROP_MULTICAST_ADDRESS:
		g_value_set_string (value, self->priv->multicast_address);
		break;
	case PROP_HONOR_INDIRECT:
		g_value_set_boolean (value, self->priv->honor_indirect);
		break;
	case PROP_MAX_DISPLAYS_PER_HOST:
		g_value_set_uint (value, self->priv->max_displays_per_host);
		break;
	case PROP_MAX_DISPLAYS:
		g_value_set_uint (value, self->priv->max_displays);
		break;
	case PROP_MAX_PENDING_DISPLAYS:
		g_value_set_uint (value, self->priv->max_pending_displays);
		break;
	case PROP_MAX_WAIT:
		g_value_set_uint (value, self->priv->max_wait);
		break;
	case PROP_WILLING_SCRIPT:
		g_value_set_string (value, self->priv->willing_script);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
gdm_xdmcp_manager_class_init (GdmXdmcpManagerClass *klass)
{
	GObjectClass   *object_class = G_OBJECT_CLASS (klass);

	object_class->get_property = gdm_xdmcp_manager_get_property;
	object_class->set_property = gdm_xdmcp_manager_set_property;
	object_class->finalize = gdm_xdmcp_manager_finalize;

        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_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_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_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_object_class_install_property (object_class,
                                         PROP_WILLING_SCRIPT,
                                         g_param_spec_string ("willing-script",
                                                              "willing-script",
                                                              "willing-script",
							      NULL,
                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
        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_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_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_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_type_class_add_private (klass, sizeof (GdmXdmcpManagerPrivate));
}

static void
gdm_xdmcp_manager_init (GdmXdmcpManager *manager)
{
	char           hostbuf[1024];
	struct utsname name;

	manager->priv = GDM_XDMCP_MANAGER_GET_PRIVATE (manager);

	manager->priv->socket_fd = -1;

	manager->priv->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);
	manager->priv->sysid = g_strconcat (name.sysname,
					    " ",
					    name.release,
					    NULL);

	manager->priv->hostname = g_strdup (hostbuf);

	manager->priv->servhost.data   = (CARD8 *) g_strdup (hostbuf);
	manager->priv->servhost.length = strlen ((char *) manager->priv->servhost.data);
}

static void
gdm_xdmcp_manager_finalize (GObject *object)
{
	GdmXdmcpManager *manager;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GDM_IS_XDMCP_MANAGER (object));

	manager = GDM_XDMCP_MANAGER (object);

	g_return_if_fail (manager->priv != NULL);

	if (manager->priv->socket_watch_id > 0) {
		g_source_remove (manager->priv->socket_watch_id);
	}

	g_slist_free (manager->priv->forward_queries);
	g_slist_free (manager->priv->managed_forwards);

	g_free (manager->priv->sysid);
	g_free (manager->priv->hostname);
	g_free (manager->priv->multicast_address);
	g_free (manager->priv->willing_script);

	/* FIXME: Free servhost */

	G_OBJECT_CLASS (gdm_xdmcp_manager_parent_class)->finalize (object);
}

GdmXdmcpManager *
gdm_xdmcp_manager_new (void)
{
	if (xdmcp_manager_object != NULL) {
		g_object_ref (xdmcp_manager_object);
	} else {
		xdmcp_manager_object = g_object_new (GDM_TYPE_XDMCP_MANAGER, NULL);
		g_object_add_weak_pointer (xdmcp_manager_object,
					   (gpointer *) &xdmcp_manager_object);
	}

	return GDM_XDMCP_MANAGER (xdmcp_manager_object);
}
