2 * Copyright (C) 2010-2011 Robert Ancell.
3 * Author: Robert Ancell <robert.ancell@canonical.com>
5 * This program is free software: you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option) any later
8 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
16 #include <X11/Xdmcp.h>
19 #include "xdmcp-server.h"
20 #include "xdmcp-protocol.h"
21 #include "xdmcp-session-private.h"
22 #include "x-authority.h"
28 static guint signals[LAST_SIGNAL] = { 0 };
30 struct XDMCPServerPrivate
32 /* Port to listen on */
35 /* Address to listen on */
36 gchar *listen_address;
38 /* Listening sockets */
39 GSocket *socket, *socket6;
41 /* Hostname to report to client */
44 /* Status to report to clients */
47 /* XDM-AUTHENTICATION-1 key */
50 /* Active XDMCP sessions */
54 G_DEFINE_TYPE (XDMCPServer, xdmcp_server, G_TYPE_OBJECT);
56 /* Maximum number of milliseconds client will resend manage requests before giving up */
57 #define MANAGE_TIMEOUT 126000
59 /* Address sort support structure */
63 GInetAddress *address;
67 xdmcp_server_new (void)
69 return g_object_new (XDMCP_SERVER_TYPE, NULL);
73 xdmcp_server_set_port (XDMCPServer *server, guint port)
75 g_return_if_fail (server != NULL);
76 server->priv->port = port;
80 xdmcp_server_get_port (XDMCPServer *server)
82 g_return_val_if_fail (server != NULL, 0);
83 return server->priv->port;
87 xdmcp_server_set_listen_address (XDMCPServer *server, const gchar *listen_address)
89 g_return_if_fail (server != NULL);
91 g_free (server->priv->listen_address);
92 server->priv->listen_address = g_strdup (listen_address);
96 xdmcp_server_get_listen_address (XDMCPServer *server)
98 g_return_val_if_fail (server != NULL, NULL);
99 return server->priv->listen_address;
103 xdmcp_server_set_hostname (XDMCPServer *server, const gchar *hostname)
105 g_return_if_fail (server != NULL);
107 g_free (server->priv->hostname);
108 server->priv->hostname = g_strdup (hostname);
112 xdmcp_server_get_hostname (XDMCPServer *server)
114 g_return_val_if_fail (server != NULL, NULL);
115 return server->priv->hostname;
119 xdmcp_server_set_status (XDMCPServer *server, const gchar *status)
121 g_return_if_fail (server != NULL);
123 g_free (server->priv->status);
124 server->priv->status = g_strdup (status);
128 xdmcp_server_get_status (XDMCPServer *server)
130 g_return_val_if_fail (server != NULL, NULL);
131 return server->priv->status;
135 xdmcp_server_set_key (XDMCPServer *server, const gchar *key)
137 g_return_if_fail (server != NULL);
138 g_free (server->priv->key);
139 server->priv->key = g_strdup (key);
143 session_timeout_cb (XDMCPSession *session)
145 session->priv->inactive_timeout = 0;
147 g_debug ("Timing out unmanaged session %d", session->priv->id);
148 g_hash_table_remove (session->priv->server->priv->sessions, GINT_TO_POINTER ((gint) session->priv->id));
152 static XDMCPSession *
153 add_session (XDMCPServer *server)
155 XDMCPSession *session;
160 id = g_random_int () & 0xFFFFFFFF;
161 } while (g_hash_table_lookup (server->priv->sessions, GINT_TO_POINTER ((gint) id)));
163 session = xdmcp_session_new (id);
164 session->priv->server = server;
165 g_hash_table_insert (server->priv->sessions, GINT_TO_POINTER ((gint) id), g_object_ref (session));
166 session->priv->inactive_timeout = g_timeout_add (MANAGE_TIMEOUT, (GSourceFunc) session_timeout_cb, session);
171 static XDMCPSession *
172 get_session (XDMCPServer *server, guint16 id)
174 return g_hash_table_lookup (server->priv->sessions, GINT_TO_POINTER ((gint) id));
178 socket_address_to_string (GSocketAddress *address)
180 gchar *inet_text, *text;
182 inet_text = g_inet_address_to_string (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)));
183 text = g_strdup_printf ("%s:%d", inet_text, g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address)));
190 send_packet (GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
192 gchar *address_string;
196 address_string = socket_address_to_string (address);
197 g_debug ("Send %s to %s", xdmcp_packet_tostring (packet), address_string);
198 g_free (address_string);
200 n_written = xdmcp_packet_encode (packet, data, 1024);
202 g_critical ("Failed to encode XDMCP packet");
205 GError *error = NULL;
207 g_socket_send_to (socket, address, (gchar *) data, n_written, NULL, &error);
209 g_warning ("Error sending packet: %s", error->message);
210 g_clear_error (&error);
215 get_authentication_name (XDMCPServer *server)
217 if (server->priv->key)
218 return "XDM-AUTHENTICATION-1";
224 handle_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, gchar **authentication_names)
226 XDMCPPacket *response;
228 gchar *authentication_name = NULL;
230 /* If no authentication requested and we are configured for none then allow */
231 if (authentication_names[0] == NULL && server->priv->key == NULL)
232 authentication_name = "";
234 for (i = authentication_names; *i; i++)
236 if (strcmp (*i, get_authentication_name (server)) == 0 && server->priv->key != NULL)
238 authentication_name = *i;
243 if (authentication_name)
245 response = xdmcp_packet_alloc (XDMCP_Willing);
246 response->Willing.authentication_name = g_strdup (authentication_name);
247 response->Willing.hostname = g_strdup (server->priv->hostname);
248 response->Willing.status = g_strdup (server->priv->status);
252 response = xdmcp_packet_alloc (XDMCP_Unwilling);
253 response->Unwilling.hostname = g_strdup (server->priv->hostname);
254 if (server->priv->key)
255 response->Unwilling.status = g_strdup_printf ("No matching authentication, server requires %s", get_authentication_name (server));
257 response->Unwilling.status = g_strdup ("No matching authentication");
260 send_packet (socket, address, response);
262 xdmcp_packet_free (response);
266 handle_forward_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
268 GSocketFamily family;
269 GInetAddress *client_inet_address;
270 GSocketAddress *client_address;
274 family = g_socket_get_family (socket);
277 case G_SOCKET_FAMILY_IPV4:
278 if (packet->ForwardQuery.client_address.length != 4)
280 g_warning ("Ignoring IPv4 XDMCP ForwardQuery with client address of length %d", packet->ForwardQuery.client_address.length);
284 case G_SOCKET_FAMILY_IPV6:
285 if (packet->ForwardQuery.client_address.length != 16)
287 g_warning ("Ignoring IPv6 XDMCP ForwardQuery with client address of length %d", packet->ForwardQuery.client_address.length);
292 g_warning ("Unknown socket family %d", family);
296 for (i = 0; i < packet->ForwardQuery.client_port.length; i++)
297 port = port << 8 | packet->ForwardQuery.client_port.data[i];
299 client_inet_address = g_inet_address_new_from_bytes (packet->ForwardQuery.client_address.data, family);
300 client_address = g_inet_socket_address_new (client_inet_address, port);
301 g_object_unref (client_inet_address);
303 handle_query (server, socket, client_address, packet->ForwardQuery.authentication_names);
305 g_object_unref (client_address);
311 if (c >= '0' && c <= '9')
313 if (c >= 'a' && c <= 'f')
315 if (c >= 'A' && c <= 'F')
321 decode_key (const gchar *key, guint8 *data)
326 if (strncmp (key, "0x", 2) == 0 || strncmp (key, "0X", 2) == 0)
328 for (i = 0; i < 8; i++)
330 if (key[i*2] == '\0')
332 data[i] |= atox (key[i*2]) << 8;
333 if (key[i*2+1] == '\0')
335 data[i] |= atox (key[i*2+1]);
340 for (i = 1; i < 8 && key[i-1]; i++)
345 static GInetAddress *
346 connection_to_address (XDMCPConnection *connection)
348 switch (connection->type)
350 case XAUTH_FAMILY_INTERNET:
351 if (connection->address.length == 4)
352 return g_inet_address_new_from_bytes (connection->address.data, G_SOCKET_FAMILY_IPV4);
355 case XAUTH_FAMILY_INTERNET6:
356 if (connection->address.length == 16)
357 return g_inet_address_new_from_bytes (connection->address.data, G_SOCKET_FAMILY_IPV6);
365 /* Sort function to order XDMCP addresses by which is best to connect to */
367 compare_addresses (gconstpointer a, gconstpointer b, gpointer user_data)
369 const AddrSortItem *item_a = a;
370 const AddrSortItem *item_b = b;
371 GInetAddress *source_address = user_data;
372 GSocketFamily family_a;
373 GSocketFamily family_b;
374 gboolean is_link_local;
376 /* Prefer non link-local addresses */
377 is_link_local = g_inet_address_get_is_link_local (item_a->address);
378 if (is_link_local != g_inet_address_get_is_link_local (item_b->address))
379 return is_link_local ? 1 : -1;
381 /* Prefer the source address family */
382 family_a = g_inet_address_get_family (item_a->address);
383 family_b = g_inet_address_get_family (item_b->address);
384 if (family_a != family_b)
386 GSocketFamily family;
388 family = g_inet_address_get_family (source_address);
389 if (family_a == family)
391 if (family_b == family)
393 return family_a < family_b ? -1 : 1;
397 if (g_inet_address_equal (item_a->address, item_b->address))
400 /* Prefer the source address */
401 if (g_inet_address_equal (source_address, item_a->address))
403 if (g_inet_address_equal (source_address, item_b->address))
406 /* Addresses are not equal, but preferences are: order is undefined */
410 static XDMCPConnection *
411 choose_connection (XDMCPPacket *packet, GInetAddress *source_address)
413 gsize addresses_length, i;
418 addresses_length = packet->Request.n_connections;
419 if (addresses_length == 0)
422 addresses = g_array_sized_new (FALSE, FALSE, sizeof addr, addresses_length);
426 for (i = 0; i < addresses_length; i++)
428 addr.address = connection_to_address (&packet->Request.connections[i]);
432 g_array_append_val (addresses, addr);
436 /* Sort the addresses according to our preferences */
437 g_array_sort_with_data (addresses, compare_addresses, source_address);
439 /* Use the best address */
441 index = g_array_index (addresses, AddrSortItem, 0).index;
443 /* Free the local sort array and items */
444 for (i = 0; i < addresses->len; i++)
445 g_object_unref (g_array_index (addresses, AddrSortItem, i).address);
446 g_object_unref (addresses);
448 return index >= 0 ? &packet->Request.connections[index] : NULL;
452 has_string (gchar **list, const gchar *text)
456 for (i = list; *i; i++)
457 if (strcmp (*i, text) == 0)
464 handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
466 XDMCPPacket *response;
467 XDMCPSession *session;
468 gchar *authentication_name = NULL, *decline_status = NULL, *authorization_name, *display_number;
469 guint8 *authentication_data = NULL, *authorization_data = NULL, *session_authorization_data = NULL;
470 gsize authentication_data_length = 0, authorization_data_length = 0, session_authorization_data_length = 0;
471 XDMCPConnection *connection;
474 /* Check authentication */
475 if (strcmp (packet->Request.authentication_name, "") == 0)
477 if (!server->priv->key)
479 if (!has_string (packet->Request.authorization_names, "MIT-MAGIC-COOKIE-1"))
480 decline_status = g_strdup ("No matching authorization, server requires MIT-MAGIC-COOKIE-1");
483 decline_status = g_strdup ("No matching authentication, server requires XDM-AUTHENTICATION-1");
485 else if (strcmp (packet->Request.authentication_name, "XDM-AUTHENTICATION-1") == 0 && server->priv->key)
487 if (packet->Request.authentication_data.length == 8)
489 guint8 input[8], key[8];
491 memcpy (input, packet->Request.authentication_data.data, packet->Request.authentication_data.length);
494 decode_key (server->priv->key, key);
496 /* Decode message from server */
497 authentication_name = g_strdup ("XDM-AUTHENTICATION-1");
498 authentication_data = g_malloc (sizeof (guint8) * 8);
499 authentication_data_length = 8;
501 XdmcpUnwrap (input, key, rho.data, authentication_data_length);
502 XdmcpIncrementKey (&rho);
503 XdmcpWrap (rho.data, key, authentication_data, authentication_data_length);
505 if (!has_string (packet->Request.authorization_names, "XDM-AUTHORIZATION-1"))
506 decline_status = g_strdup ("No matching authorization, server requires XDM-AUTHORIZATION-1");
509 decline_status = g_strdup ("Invalid XDM-AUTHENTICATION-1 data provided");
513 if (strcmp (packet->Request.authentication_name, "") == 0)
514 decline_status = g_strdup_printf ("No matching authentication, server does not support unauthenticated connections");
515 else if (server->priv->key)
516 decline_status = g_strdup ("No matching authentication, server requires XDM-AUTHENTICATION-1");
518 decline_status = g_strdup ("No matching authentication, server only supports unauthenticated connections");
521 /* Choose an address to connect back on */
522 connection = choose_connection (packet, g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)));
523 if (!connection && !decline_status)
524 decline_status = g_strdup ("No valid address found");
526 if (!authentication_name)
527 authentication_name = g_strdup ("");
529 /* Decline if request was not valid */
532 response = xdmcp_packet_alloc (XDMCP_Decline);
533 response->Decline.status = decline_status;
534 response->Decline.authentication_name = authentication_name;
535 response->Decline.authentication_data.data = authentication_data;
536 response->Decline.authentication_data.length = authentication_data_length;
537 send_packet (socket, address, response);
538 xdmcp_packet_free (response);
542 /* Generate authorization data */
543 if (server->priv->key)
546 guint8 key[8], session_key[8];
549 decode_key (server->priv->key, key);
551 /* Generate a private session key */
552 // FIXME: Pick a good DES key?
554 for (i = 1; i < 8; i++)
555 session_key[i] = g_random_int () & 0xFF;
557 /* Encrypt the session key and send it to the server */
558 authorization_data = g_malloc (8);
559 authorization_data_length = 8;
560 XdmcpWrap (session_key, key, authorization_data, authorization_data_length);
562 /* Authorization data is the number received from the client followed by the private session key */
563 authorization_name = g_strdup ("XDM-AUTHORIZATION-1");
564 session_authorization_data = g_malloc (16);
565 session_authorization_data_length = 16;
566 XdmcpDecrementKey (&rho);
567 memcpy (session_authorization_data, rho.data, 8);
568 memcpy (session_authorization_data + 8, session_key, 8);
574 /* Data is the cookie */
575 auth = x_authority_new_cookie (XAUTH_FAMILY_WILD, NULL, 0, "");
576 authorization_data = x_authority_copy_authorization_data (auth);
577 authorization_data_length = x_authority_get_authorization_data_length (auth);
578 authorization_name = g_strdup ("MIT-MAGIC-COOKIE-1");
579 session_authorization_data = x_authority_copy_authorization_data (auth);
580 session_authorization_data_length = x_authority_get_authorization_data_length (auth);
582 g_object_unref (auth);
585 session = add_session (server);
586 session->priv->address = connection_to_address (connection);
587 session->priv->display_number = packet->Request.display_number;
588 display_number = g_strdup_printf ("%d", packet->Request.display_number);
590 /* We need to check if this is the loopback address and set the authority
591 * for a local connection if this is so as XCB treats "127.0.0.1" as local
593 if (g_inet_address_get_is_loopback (session->priv->address))
595 gchar hostname[1024];
596 gethostname (hostname, 1024);
598 session->priv->authority = x_authority_new (XAUTH_FAMILY_LOCAL,
603 session_authorization_data,
604 session_authorization_data_length);
607 session->priv->authority = x_authority_new (connection->type,
608 connection->address.data,
609 connection->address.length,
612 session_authorization_data,
613 session_authorization_data_length);
614 g_free (display_number);
616 response = xdmcp_packet_alloc (XDMCP_Accept);
617 response->Accept.session_id = xdmcp_session_get_id (session);
618 response->Accept.authentication_name = authentication_name;
619 response->Accept.authentication_data.data = authentication_data;
620 response->Accept.authentication_data.length = authentication_data_length;
621 response->Accept.authorization_name = authorization_name;
622 response->Accept.authorization_data.data = authorization_data;
623 response->Accept.authorization_data.length = authorization_data_length;
624 send_packet (socket, address, response);
625 xdmcp_packet_free (response);
629 handle_manage (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
631 XDMCPSession *session;
634 session = get_session (server, packet->Manage.session_id);
637 XDMCPPacket *response;
639 response = xdmcp_packet_alloc (XDMCP_Refuse);
640 response->Refuse.session_id = packet->Manage.session_id;
641 send_packet (socket, address, response);
642 xdmcp_packet_free (response);
647 /* Ignore duplicate requests */
648 if (session->priv->started)
650 if (session->priv->display_number != packet->Manage.display_number ||
651 strcmp (session->priv->display_class, packet->Manage.display_class) != 0)
652 g_debug ("Ignoring duplicate Manage with different data");
656 /* Reject if has changed display number */
657 if (packet->Manage.display_number != session->priv->display_number)
659 XDMCPPacket *response;
661 g_debug ("Received Manage for display number %d, but Request was %d", packet->Manage.display_number, session->priv->display_number);
662 response = xdmcp_packet_alloc (XDMCP_Refuse);
663 response->Refuse.session_id = packet->Manage.session_id;
664 send_packet (socket, address, response);
665 xdmcp_packet_free (response);
668 session->priv->display_class = g_strdup (packet->Manage.display_class);
670 g_signal_emit (server, signals[NEW_SESSION], 0, session, &result);
673 /* Cancel the inactive timer */
674 if (session->priv->inactive_timeout)
675 g_source_remove (session->priv->inactive_timeout);
677 session->priv->started = TRUE;
681 XDMCPPacket *response;
683 response = xdmcp_packet_alloc (XDMCP_Failed);
684 response->Failed.session_id = packet->Manage.session_id;
685 response->Failed.status = g_strdup_printf ("Failed to connect to display :%d", packet->Manage.display_number);
686 send_packet (socket, address, response);
687 xdmcp_packet_free (response);
692 handle_keep_alive (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
694 XDMCPPacket *response;
695 XDMCPSession *session;
696 gboolean alive = FALSE;
698 session = get_session (server, packet->KeepAlive.session_id);
700 alive = TRUE; // FIXME: xdmcp_session_get_alive (session);
702 response = xdmcp_packet_alloc (XDMCP_Alive);
703 response->Alive.session_running = alive;
704 response->Alive.session_id = alive ? packet->KeepAlive.session_id : 0;
705 send_packet (socket, address, response);
706 xdmcp_packet_free (response);
710 read_cb (GSocket *socket, GIOCondition condition, XDMCPServer *server)
712 GSocketAddress *address;
714 GError *error = NULL;
717 n_read = g_socket_receive_from (socket, &address, data, 1024, NULL, &error);
719 g_warning ("Failed to read from XDMCP socket: %s", error->message);
720 g_clear_error (&error);
726 packet = xdmcp_packet_decode ((guint8 *)data, n_read);
729 gchar *packet_string, *address_string;
731 packet_string = xdmcp_packet_tostring (packet);
732 address_string = socket_address_to_string (address);
733 g_debug ("Got %s from %s", packet_string, address_string);
734 g_free (packet_string);
735 g_free (address_string);
737 switch (packet->opcode)
739 case XDMCP_BroadcastQuery:
741 case XDMCP_IndirectQuery:
742 handle_query (server, socket, address, packet->Query.authentication_names);
744 case XDMCP_ForwardQuery:
745 handle_forward_query (server, socket, address, packet);
748 handle_request (server, socket, address, packet);
751 handle_manage (server, socket, address, packet);
753 case XDMCP_KeepAlive:
754 handle_keep_alive (server, socket, address, packet);
757 g_warning ("Got unexpected XDMCP packet %d", packet->opcode);
761 xdmcp_packet_free (packet);
769 open_udp_socket (GSocketFamily family, guint port, const gchar *listen_address, GError **error)
772 GSocketAddress *address;
775 socket = g_socket_new (family, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, error);
783 addresses = g_resolver_lookup_by_name (g_resolver_get_default (), listen_address, NULL, error);
786 g_object_unref (socket);
789 address = g_inet_socket_address_new (addresses->data, port);
790 g_resolver_free_addresses (addresses);
793 address = g_inet_socket_address_new (g_inet_address_new_any (family), port);
794 result = g_socket_bind (socket, address, TRUE, error);
797 g_object_unref (socket);
805 xdmcp_server_start (XDMCPServer *server)
808 GError *error = NULL;
810 g_return_val_if_fail (server != NULL, FALSE);
812 server->priv->socket = open_udp_socket (G_SOCKET_FAMILY_IPV4, server->priv->port, server->priv->listen_address, &error);
814 g_warning ("Failed to create IPv4 XDMCP socket: %s", error->message);
815 g_clear_error (&error);
817 if (server->priv->socket)
819 source = g_socket_create_source (server->priv->socket, G_IO_IN, NULL);
820 g_source_set_callback (source, (GSourceFunc) read_cb, server, NULL);
821 g_source_attach (source, NULL);
824 server->priv->socket6 = open_udp_socket (G_SOCKET_FAMILY_IPV6, server->priv->port, server->priv->listen_address, &error);
826 g_warning ("Failed to create IPv6 XDMCP socket: %s", error->message);
827 g_clear_error (&error);
829 if (server->priv->socket6)
831 source = g_socket_create_source (server->priv->socket6, G_IO_IN, NULL);
832 g_source_set_callback (source, (GSourceFunc) read_cb, server, NULL);
833 g_source_attach (source, NULL);
836 if (!server->priv->socket && !server->priv->socket6)
843 xdmcp_server_init (XDMCPServer *server)
845 server->priv = G_TYPE_INSTANCE_GET_PRIVATE (server, XDMCP_SERVER_TYPE, XDMCPServerPrivate);
847 server->priv->port = XDM_UDP_PORT;
848 server->priv->hostname = g_strdup ("");
849 server->priv->status = g_strdup ("");
850 server->priv->sessions = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_object_unref);
854 xdmcp_server_finalize (GObject *object)
856 XDMCPServer *self = XDMCP_SERVER (object);
858 g_clear_object (&self->priv->socket);
859 g_clear_object (&self->priv->socket6);
860 g_free (self->priv->listen_address);
861 g_free (self->priv->hostname);
862 g_free (self->priv->status);
863 g_free (self->priv->key);
864 g_hash_table_unref (self->priv->sessions);
866 G_OBJECT_CLASS (xdmcp_server_parent_class)->finalize (object);
870 xdmcp_server_class_init (XDMCPServerClass *klass)
872 GObjectClass *object_class = G_OBJECT_CLASS (klass);
874 object_class->finalize = xdmcp_server_finalize;
876 g_type_class_add_private (klass, sizeof (XDMCPServerPrivate));
878 signals[NEW_SESSION] =
879 g_signal_new (XDMCP_SERVER_SIGNAL_NEW_SESSION,
880 G_TYPE_FROM_CLASS (klass),
882 G_STRUCT_OFFSET (XDMCPServerClass, new_session),
883 g_signal_accumulator_true_handled,
886 G_TYPE_BOOLEAN, 1, XDMCP_SESSION_TYPE);