2 * Copyright (C) 2010 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"
28 PROP_AUTHENTICATION_KEY
35 static guint signals[LAST_SIGNAL] = { 0 };
37 struct XDMCPServerPrivate
49 gchar *authentication_key_string;
54 G_DEFINE_TYPE (XDMCPServer, xdmcp_server, G_TYPE_OBJECT);
57 xdmcp_server_new (void)
59 return g_object_new (XDMCP_SERVER_TYPE, NULL);
63 xdmcp_server_set_port (XDMCPServer *server, guint port)
65 server->priv->port = port;
69 xdmcp_server_get_port (XDMCPServer *server)
71 return server->priv->port;
75 xdmcp_server_set_hostname (XDMCPServer *server, const gchar *hostname)
77 g_free (server->priv->hostname);
78 server->priv->hostname = g_strdup (hostname);
82 xdmcp_server_get_hostname (XDMCPServer *server)
84 return server->priv->hostname;
88 xdmcp_server_set_status (XDMCPServer *server, const gchar *status)
90 g_free (server->priv->status);
91 server->priv->status = g_strdup (status);
95 xdmcp_server_get_status (XDMCPServer *server)
97 return server->priv->status;
101 xdmcp_server_set_authentication_key (XDMCPServer *server, const gchar *key_string)
103 g_free (server->priv->authentication_key_string);
104 server->priv->authentication_key_string = g_strdup (key_string);
108 xdmcp_server_get_authentication_key (XDMCPServer *server)
110 return server->priv->authentication_key_string;
113 static XDMCPSession *
114 add_session (XDMCPServer *server)
116 XDMCPSession *session;
121 id = g_random_int () & 0xFFFFFFFF;
122 } while (g_hash_table_lookup (server->priv->sessions, GINT_TO_POINTER ((gint) id)));
124 session = xdmcp_session_new (id);
125 g_hash_table_insert (server->priv->sessions, GINT_TO_POINTER ((gint) id), g_object_ref (session));
130 static XDMCPSession *
131 get_session (XDMCPServer *server, guint16 id)
133 return g_hash_table_lookup (server->priv->sessions, GINT_TO_POINTER ((gint) id));
137 send_packet (GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
142 g_debug ("Send %s", xdmcp_packet_tostring (packet));
144 n_written = xdmcp_packet_encode (packet, data, 1024);
146 g_critical ("Failed to encode XDMCP packet");
149 GError *error = NULL;
151 if (g_socket_send_to (socket, address, (gchar *) data, n_written, NULL, &error) < 0)
152 g_warning ("Error sending packet: %s", error->message);
154 g_clear_error (&error);
159 handle_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
161 XDMCPPacket *response;
162 const gchar *authentication_name = "";
165 /* Use authentication if we are configured for it */
166 for (i = packet->Query.authentication_names; *i; i++)
168 if (strcmp (*i, "XDM-AUTHENTICATION-1") == 0)
170 if (server->priv->authentication_key_string)
171 authentication_name = "XDM-AUTHENTICATION-1";
175 response = xdmcp_packet_alloc (XDMCP_Willing);
176 response->Willing.authentication_name = g_strdup (authentication_name);
177 response->Willing.hostname = g_strdup (server->priv->hostname);
178 response->Willing.status = g_strdup (server->priv->status);
180 send_packet (socket, address, response);
182 xdmcp_packet_free (response);
188 if (c >= '0' && c <= '9')
190 if (c >= 'a' && c <= 'f')
192 if (c >= 'A' && c <= 'F')
198 string_to_key (XdmAuthKeyRec *key, const gchar *string)
202 if (strncmp (string, "0x", 2) == 0 || strncmp (string, "0X", 2) == 0)
206 for (i = 0; i < 8 && string[j]; i++)
212 key->data[i] |= atox (string[j]) >> 8;
216 key->data[i] |= atox (string[j+1]);
225 for (i = 1; i < 8 && string[i-1]; i++)
226 key->data[i] = string[i-1];
233 handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
236 XDMCPPacket *response;
237 XDMCPSession *session;
238 const gchar *authorization_name = "";
239 guchar *authentication_data = NULL;
240 gsize authentication_data_length = 0;
242 GInetAddress *address4 = NULL; /*, *address6 = NULL;*/
244 /* FIXME: If session not started (i.e. not received the Manage then response with Accept again) */
246 /* FIXME: Perform requested authentication */
247 if (strcmp (packet->Request.authentication_name, "") == 0)
248 ; /* No authentication */
249 else if (strcmp (packet->Request.authentication_name, "XDM-AUTHENTICATION-1") == 0)
251 XdmAuthKeyRec key, message;
253 if (!server->priv->authentication_key_string)
255 // FIXME: Send Decline
259 // FIXME: I don't think it technically has to be 8 but the Xdmcp library requires it
260 if (packet->Request.authentication_data.length != 8)
262 // FIXME: Send Decline
267 string_to_key (&key, server->priv->authentication_key_string);
269 /* Decode message from server */
270 authentication_data = g_malloc (sizeof (guchar) * packet->Request.authentication_data.length);
271 authentication_data_length = packet->Request.authentication_data.length;
273 XdmcpUnwrap (packet->Request.authentication_data.data, key.data, message.data, authentication_data_length);
274 XdmcpIncrementKey (&message);
275 XdmcpWrap (message.data, key.data, authentication_data, authentication_data_length);
277 //authorization_name = "XDM_AUTHORIZATION-1";
281 // FIXME: Send Decline
285 /* Choose an authorization from the list */
286 for (j = packet->Request.authorization_names; *j; j++)
288 if (strcmp (*j, "MIT-MAGIC-COOKIE-1") == 0)
290 // FIXME: Generate cookie
292 else if (strcmp (*j, "XDM-AUTHORIZATION-1") == 0)
297 // FIXME: Check have supported authorization
299 for (i = 0; i < packet->Request.n_connections; i++)
301 XDMCPConnection *connection;
303 connection = &packet->Request.connections[i];
304 switch (connection->type)
307 if (connection->address.length == 4)
308 address4 = g_inet_address_new_from_bytes (connection->address.data, G_SOCKET_FAMILY_IPV4);
310 /*case FamilyInternet6:
311 if (connection->address.length == 16)
312 address6 = g_inet_address_new_from_bytes (connection->address.data, G_SOCKET_FAMILY_IPV6);
319 response = xdmcp_packet_alloc (XDMCP_Decline);
320 response->Decline.status = g_strdup ("No valid address found");
321 response->Decline.authentication_name = g_strdup (packet->Request.authentication_name);
322 response->Accept.authentication_data.data = authentication_data;
323 response->Accept.authentication_data.length = authentication_data_length;
324 send_packet (socket, address, response);
325 xdmcp_packet_free (response);
329 /* FIXME: Allow a higher layer to decline */
331 session = add_session (server);
332 session->priv->address = address4; /*address6 ? address6 : address4;*/
333 // FIXME: Timeout inactive sessions?
335 response = xdmcp_packet_alloc (XDMCP_Accept);
336 response->Accept.session_id = xdmcp_session_get_id (session);
337 response->Accept.authentication_name = g_strdup (packet->Request.authentication_name);
338 response->Accept.authentication_data.data = authentication_data;
339 response->Accept.authentication_data.length = authentication_data_length;
340 response->Accept.authorization_name = g_strdup (authorization_name);
341 send_packet (socket, address, response);
342 xdmcp_packet_free (response);
346 handle_manage (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
348 XDMCPSession *session;
350 session = get_session (server, packet->Manage.session_id);
353 gchar *ip_address, *display_address;
355 /* Ignore duplicate requests */
356 if (session->priv->started)
358 if (session->priv->display_number != packet->Manage.display_number ||
359 strcmp (session->priv->display_class, packet->Manage.display_class) != 0)
360 g_warning ("Duplicate Manage received with different data");
364 /* Try and connect */
365 ip_address = g_inet_address_to_string (G_INET_ADDRESS (session->priv->address));
366 display_address = g_strdup_printf ("%s:%d", ip_address, packet->Manage.display_number);
368 session->priv->connection = xcb_connect (display_address, NULL);
369 // TODO: xcb_connect_to_display_with_auth_info (display_address, NULL);
371 if (!session->priv->connection)
373 XDMCPPacket *response;
375 response = xdmcp_packet_alloc (XDMCP_Failed);
376 response->Failed.session_id = packet->Manage.session_id;
377 response->Failed.status = g_strdup_printf ("Failed to connect to display %s", display_address);
378 send_packet (socket, address, response);
379 xdmcp_packet_free (response);
383 session->priv->started = TRUE;
384 session->priv->display_number = packet->Manage.display_number;
385 session->priv->display_class = g_strdup (packet->Manage.display_class);
386 g_signal_emit (server, signals[SESSION_ADDED], 0, session);
389 g_free (display_address);
393 XDMCPPacket *response;
395 response = xdmcp_packet_alloc (XDMCP_Refuse);
396 response->Refuse.session_id = packet->Manage.session_id;
397 send_packet (socket, address, response);
398 xdmcp_packet_free (response);
403 handle_keep_alive (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
405 XDMCPPacket *response;
406 XDMCPSession *session;
407 gboolean alive = FALSE;
409 session = get_session (server, packet->KeepAlive.session_id);
411 alive = TRUE; //xdmcp_session_get_alive (session);
413 response = xdmcp_packet_alloc (XDMCP_Alive);
414 response->Alive.session_running = alive;
415 response->Alive.session_id = alive ? packet->KeepAlive.session_id : 0;
416 send_packet (socket, address, response);
417 xdmcp_packet_free (response);
421 read_cb (GSocket *socket, GIOCondition condition, XDMCPServer *server)
423 GSocketAddress *address;
425 GError *error = NULL;
428 n_read = g_socket_receive_from (socket, &address, data, 1024, NULL, &error);
433 packet = xdmcp_packet_decode ((guchar *)data, n_read);
436 g_debug ("Got %s", xdmcp_packet_tostring (packet));
438 switch (packet->opcode)
440 case XDMCP_BroadcastQuery:
442 case XDMCP_IndirectQuery:
443 handle_query (server, socket, address, packet);
446 handle_request (server, socket, address, packet);
449 handle_manage (server, socket, address, packet);
451 case XDMCP_KeepAlive:
452 handle_keep_alive (server, socket, address, packet);
455 g_warning ("Got unexpected XDMCP packet %d", packet->opcode);
459 xdmcp_packet_free (packet);
463 g_warning ("Failed to read from XDMCP socket: %s", error->message);
465 g_clear_error (&error);
471 xdmcp_server_start (XDMCPServer *server)
473 GSocketAddress *address;
476 GError *error = NULL;
478 server->priv->socket = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_DATAGRAM, G_SOCKET_PROTOCOL_UDP, &error);
479 if (!server->priv->socket)
480 g_warning ("Failed to create XDMCP socket: %s", error->message);
481 g_clear_error (&error);
482 if (!server->priv->socket)
485 address = g_inet_socket_address_new (g_inet_address_new_any (G_SOCKET_FAMILY_IPV4), server->priv->port);
486 result = g_socket_bind (server->priv->socket, address, TRUE, &error);
488 g_warning ("Failed to bind XDMCP server port: %s", error->message);
489 g_clear_error (&error);
493 source = g_socket_create_source (server->priv->socket, G_IO_IN, NULL);
494 g_source_set_callback (source, (GSourceFunc) read_cb, server, NULL);
495 g_source_attach (source, NULL);
501 xdmcp_server_init (XDMCPServer *server)
503 server->priv = G_TYPE_INSTANCE_GET_PRIVATE (server, XDMCP_SERVER_TYPE, XDMCPServerPrivate);
505 server->priv->port = XDM_UDP_PORT;
506 server->priv->hostname = g_strdup ("");
507 server->priv->status = g_strdup ("");
508 server->priv->sessions = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
512 xdmcp_server_set_property (GObject *object,
519 self = XDMCP_SERVER (object);
523 xdmcp_server_set_hostname (self, g_value_get_string (value));
526 xdmcp_server_set_status (self, g_value_get_string (value));
528 case PROP_AUTHENTICATION_KEY:
529 xdmcp_server_set_authentication_key (self, g_value_get_string (value));
532 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
538 xdmcp_server_get_property (GObject *object,
545 self = XDMCP_SERVER (object);
549 g_value_set_string (value, self->priv->hostname);
552 g_value_set_string (value, self->priv->status);
554 case PROP_AUTHENTICATION_KEY:
555 g_value_set_string (value, xdmcp_server_get_authentication_key (self));
558 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
564 xdmcp_server_finalize (GObject *object)
568 self = XDMCP_SERVER (object);
570 if (self->priv->socket)
571 g_object_unref (self->priv->socket);
572 g_free (self->priv->hostname);
573 g_free (self->priv->status);
574 g_free (self->priv->authentication_key_string);
575 g_hash_table_unref (self->priv->sessions);
579 xdmcp_server_class_init (XDMCPServerClass *klass)
581 GObjectClass *object_class = G_OBJECT_CLASS (klass);
583 object_class->set_property = xdmcp_server_set_property;
584 object_class->get_property = xdmcp_server_get_property;
585 object_class->finalize = xdmcp_server_finalize;
587 g_type_class_add_private (klass, sizeof (XDMCPServerPrivate));
589 g_object_class_install_property (object_class,
591 g_param_spec_int ("port",
593 "UDP/IP port to listen on",
594 1, G_MAXUINT16, XDM_UDP_PORT,
595 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
596 g_object_class_install_property (object_class,
598 g_param_spec_string ("hostname",
603 g_object_class_install_property (object_class,
605 g_param_spec_string ("status",
610 g_object_class_install_property (object_class,
611 PROP_AUTHENTICATION_KEY,
612 g_param_spec_string ("authentication-key",
613 "authentication-key",
614 "Authentication key",
618 signals[SESSION_ADDED] =
619 g_signal_new ("session-added",
620 G_TYPE_FROM_CLASS (klass),
622 G_STRUCT_OFFSET (XDMCPServerClass, session_added),
624 g_cclosure_marshal_VOID__OBJECT,
625 G_TYPE_NONE, 1, XDMCP_SESSION_TYPE);