]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blobdiff - src/xdmcp-server.c
Releasing 1.20.0
[sojka/lightdm.git] / src / xdmcp-server.c
index 4761b7b071385e984dd553768740982243445a19..bbc11927c804e85d0aa56b207a4f5f988b27938d 100644 (file)
@@ -56,6 +56,13 @@ G_DEFINE_TYPE (XDMCPServer, xdmcp_server, G_TYPE_OBJECT);
 /* Maximum number of milliseconds client will resend manage requests before giving up */
 #define MANAGE_TIMEOUT 126000
 
+/* Address sort support structure */
+typedef struct
+{
+    gsize index;
+    GInetAddress *address;
+} AddrSortItem;
+
 XDMCPServer *
 xdmcp_server_new (void)
 {
@@ -167,13 +174,28 @@ get_session (XDMCPServer *server, guint16 id)
     return g_hash_table_lookup (server->priv->sessions, GINT_TO_POINTER ((gint) id));
 }
 
+static gchar *
+socket_address_to_string (GSocketAddress *address)
+{
+    gchar *inet_text, *text;
+
+    inet_text = g_inet_address_to_string (g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)));
+    text = g_strdup_printf ("%s:%d", inet_text, g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (address)));
+    g_free (inet_text);
+
+    return text;
+}
+
 static void
 send_packet (GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
 {
+    gchar *address_string;
     guint8 data[1024];
     gssize n_written;
 
-    g_debug ("Send %s", xdmcp_packet_tostring (packet));
+    address_string = socket_address_to_string (address);
+    g_debug ("Send %s to %s", xdmcp_packet_tostring (packet), address_string);
+    g_free (address_string);
 
     n_written = xdmcp_packet_encode (packet, data, 1024);
     if (n_written < 0)
@@ -199,17 +221,17 @@ get_authentication_name (XDMCPServer *server)
 }
 
 static void
-handle_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
+handle_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, gchar **authentication_names)
 {
     XDMCPPacket *response;
     gchar **i;
     gchar *authentication_name = NULL;
 
     /* If no authentication requested and we are configured for none then allow */
-    if (packet->Query.authentication_names[0] == NULL && server->priv->key == NULL)
+    if (authentication_names[0] == NULL && server->priv->key == NULL)
         authentication_name = "";
 
-    for (i = packet->Query.authentication_names; *i; i++)
+    for (i = authentication_names; *i; i++)
     {
         if (strcmp (*i, get_authentication_name (server)) == 0 && server->priv->key != NULL)
         {
@@ -232,7 +254,7 @@ handle_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDM
         if (server->priv->key)
             response->Unwilling.status = g_strdup_printf ("No matching authentication, server requires %s", get_authentication_name (server));
         else
-            response->Unwilling.status = g_strdup ("Server does not support authentication");
+            response->Unwilling.status = g_strdup ("No matching authentication");
     }
 
     send_packet (socket, address, response);
@@ -240,6 +262,49 @@ handle_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDM
     xdmcp_packet_free (response);
 }
 
+static void
+handle_forward_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
+{
+    GSocketFamily family;
+    GInetAddress *client_inet_address;
+    GSocketAddress *client_address;
+    gint i;
+    guint16 port = 0;
+
+    family = g_socket_get_family (socket);
+    switch (family)
+    {
+    case G_SOCKET_FAMILY_IPV4:
+        if (packet->ForwardQuery.client_address.length != 4)
+        {
+            g_warning ("Ignoring IPv4 XDMCP ForwardQuery with client address of length %d", packet->ForwardQuery.client_address.length);
+            return;
+        }
+        break;
+    case G_SOCKET_FAMILY_IPV6:
+        if (packet->ForwardQuery.client_address.length != 16)
+        {
+            g_warning ("Ignoring IPv6 XDMCP ForwardQuery with client address of length %d", packet->ForwardQuery.client_address.length);
+            return;
+        }
+        break;
+    default:
+        g_warning ("Unknown socket family %d", family);
+        return;
+    }
+
+    for (i = 0; i < packet->ForwardQuery.client_port.length; i++)
+        port = port << 8 | packet->ForwardQuery.client_port.data[i];
+
+    client_inet_address = g_inet_address_new_from_bytes (packet->ForwardQuery.client_address.data, family);
+    client_address = g_inet_socket_address_new (client_inet_address, port);
+    g_object_unref (client_inet_address);
+
+    handle_query (server, socket, client_address, packet->ForwardQuery.authentication_names);
+
+    g_object_unref (client_address);
+}
+
 static guint8
 atox (char c)
 {
@@ -297,51 +362,102 @@ connection_to_address (XDMCPConnection *connection)
     }
 }
 
-static gssize
-find_address (GInetAddress **addresses, gsize length, GSocketFamily family)
+/* Sort function to order XDMCP addresses by which is best to connect to */
+static gint
+compare_addresses (gconstpointer a, gconstpointer b, gpointer user_data)
 {
-    int i;
-
-    for (i = 0; i < length; i++)
+    const AddrSortItem *item_a = a;
+    const AddrSortItem *item_b = b;
+    GInetAddress *source_address = user_data;
+    GSocketFamily family_a;
+    GSocketFamily family_b;
+    gboolean is_link_local;
+
+    /* Prefer non link-local addresses */
+    is_link_local = g_inet_address_get_is_link_local (item_a->address);
+    if (is_link_local != g_inet_address_get_is_link_local (item_b->address))
+        return is_link_local ? 1 : -1;
+
+    /* Prefer the source address family */
+    family_a = g_inet_address_get_family (item_a->address);
+    family_b = g_inet_address_get_family (item_b->address);
+    if (family_a != family_b)
     {
-        GInetAddress *address = addresses[i];
-        if (address && g_inet_address_get_family (address) == family)
-            return i;
+        GSocketFamily family;
+
+        family = g_inet_address_get_family (source_address);
+        if (family_a == family)
+            return -1;
+        if (family_b == family)
+            return 1;
+        return family_a < family_b ? -1 : 1;
     }
 
-    return -1;
+    /* Check equality */
+    if (g_inet_address_equal (item_a->address, item_b->address))
+        return 0;
+
+    /* Prefer the source address */
+    if (g_inet_address_equal (source_address, item_a->address))
+        return -1;
+    if (g_inet_address_equal (source_address, item_b->address))
+        return 1;
+
+    /* Addresses are not equal, but preferences are: order is undefined */
+    return 0;
 }
 
 static XDMCPConnection *
 choose_connection (XDMCPPacket *packet, GInetAddress *source_address)
 {
-    GInetAddress **addresses;
     gsize addresses_length, i;
+    GArray *addresses;
     gssize index = -1;
+    AddrSortItem addr;
 
     addresses_length = packet->Request.n_connections;
-    addresses = malloc (sizeof (GInetAddress *) * addresses_length);
+    if (addresses_length == 0)
+        return NULL;
+
+    addresses = g_array_sized_new (FALSE, FALSE, sizeof addr, addresses_length);
+    if (!addresses)
+        return NULL;
+
     for (i = 0; i < addresses_length; i++)
-        addresses[i] = connection_to_address (&packet->Request.connections[i]);
+    {
+        addr.address = connection_to_address (&packet->Request.connections[i]);
+        if (addr.address)
+        {
+            addr.index = i;
+            g_array_append_val (addresses, addr);
+        }
+    }
 
-    /* Use the address the request came in on as this is the least likely to have firewall / routing issues */
-    for (i = 0; i < addresses_length && index < 0; i++)
-        if (g_inet_address_equal (source_address, addresses[i]))
-            index = i;
+    /* Sort the addresses according to our preferences */
+    g_array_sort_with_data (addresses, compare_addresses, source_address);
 
-    /* Otherwise try and find an address that matches the incoming type */
-    if (index < 0)
-        index = find_address (addresses, addresses_length, g_inet_address_get_family (source_address));
+    /* Use the best address */
+    if (addresses->len)
+        index = g_array_index (addresses, AddrSortItem, 0).index;
 
-    /* Otherwise use the first available */
-    if (index < 0 && addresses_length > 0)
-        index = 0;
+    /* Free the local sort array and items */
+    for (i = 0; i < addresses->len; i++)
+        g_object_unref (g_array_index (addresses, AddrSortItem, i).address);
+    g_object_unref (addresses);
 
-    for (i = 0; i < addresses_length; i++)
-        g_object_unref (addresses[i]);
-    g_free (addresses);
+    return index >= 0 ? &packet->Request.connections[index] : NULL;
+}
+
+static gboolean
+has_string (gchar **list, const gchar *text)
+{
+    gchar **i;
 
-    return &packet->Request.connections[index];
+    for (i = list; *i; i++)
+        if (strcmp (*i, text) == 0)
+            return TRUE;
+  
+    return FALSE;
 }
 
 static void
@@ -349,90 +465,73 @@ handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, X
 {
     XDMCPPacket *response;
     XDMCPSession *session;
-    guint8 *authentication_data = NULL;
-    gsize authentication_data_length = 0;
-    gboolean match_authorization = FALSE;
-    gchar *authorization_name;
-    guint8 *authorization_data = NULL;
-    gsize authorization_data_length = 0;
-    guint8 *session_authorization_data = NULL;
-    gsize session_authorization_data_length = 0;
-    gchar **j;
+    gchar *authentication_name = NULL, *decline_status = NULL, *authorization_name, *display_number;
+    guint8 *authentication_data = NULL, *authorization_data = NULL, *session_authorization_data = NULL;
+    gsize authentication_data_length = 0, authorization_data_length = 0, session_authorization_data_length = 0;
     XDMCPConnection *connection;
-    gchar *display_number;
     XdmAuthKeyRec rho;
 
-    /* Choose an address to connect back on */
-    connection = choose_connection (packet, g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)));
-
-    /* Decline if haven't got an address we can connect on */
-    if (!connection)
+    /* Check authentication */
+    if (strcmp (packet->Request.authentication_name, "") == 0)
     {
-        response = xdmcp_packet_alloc (XDMCP_Decline);
-        response->Decline.status = g_strdup ("No valid address found");
-        response->Decline.authentication_name = g_strdup (packet->Request.authentication_name);
-        response->Decline.authentication_data.data = authentication_data;
-        response->Decline.authentication_data.length = authentication_data_length;
-        send_packet (socket, address, response);
-        xdmcp_packet_free (response);
-        return;
-    }
-
-    /* Must be using our authentication scheme */
-    if (strcmp (packet->Request.authentication_name, get_authentication_name (server)) != 0)
-    {
-        response = xdmcp_packet_alloc (XDMCP_Decline);
-        if (server->priv->key)
-            response->Decline.status = g_strdup_printf ("Server only supports %s authentication", get_authentication_name (server));
+        if (!server->priv->key)
+        {
+            if (!has_string (packet->Request.authorization_names, "MIT-MAGIC-COOKIE-1"))
+                decline_status = g_strdup ("No matching authorization, server requires MIT-MAGIC-COOKIE-1");
+        }
         else
-            response->Decline.status = g_strdup ("Server does not support authentication");
-        response->Decline.authentication_name = g_strdup ("");
-        send_packet (socket, address, response);
-        xdmcp_packet_free (response);
-        return;
+            decline_status = g_strdup ("No matching authentication, server requires XDM-AUTHENTICATION-1");
     }
-
-    /* Perform requested authentication */
-    if (server->priv->key)
+    else if (strcmp (packet->Request.authentication_name, "XDM-AUTHENTICATION-1") == 0 && server->priv->key)
     {
-        guint8 input[8], key[8];
-
-        memset (input, 0, 8);
-        memcpy (input, packet->Request.authentication_data.data, packet->Request.authentication_data.length > 8 ? 8 : packet->Request.authentication_data.length);
+        if (packet->Request.authentication_data.length == 8)
+        {
+            guint8 input[8], key[8];
 
-        /* Setup key */
-        decode_key (server->priv->key, key);
+            memcpy (input, packet->Request.authentication_data.data, packet->Request.authentication_data.length);
 
-        /* Decode message from server */
-        authentication_data = g_malloc (sizeof (guint8) * 8);
-        authentication_data_length = 8;
+            /* Setup key */
+            decode_key (server->priv->key, key);
 
-        XdmcpUnwrap (input, key, rho.data, authentication_data_length);
-        XdmcpIncrementKey (&rho);
-        XdmcpWrap (rho.data, key, authentication_data, authentication_data_length);
+            /* Decode message from server */
+            authentication_name = g_strdup ("XDM-AUTHENTICATION-1");
+            authentication_data = g_malloc (sizeof (guint8) * 8);
+            authentication_data_length = 8;
+    
+            XdmcpUnwrap (input, key, rho.data, authentication_data_length);
+            XdmcpIncrementKey (&rho);
+            XdmcpWrap (rho.data, key, authentication_data, authentication_data_length);
 
-        authorization_name = g_strdup ("XDM-AUTHORIZATION-1");
+            if (!has_string (packet->Request.authorization_names, "XDM-AUTHORIZATION-1"))
+                decline_status = g_strdup ("No matching authorization, server requires XDM-AUTHORIZATION-1");
+        }
+        else
+            decline_status = g_strdup ("Invalid XDM-AUTHENTICATION-1 data provided");
     }
     else
-        authorization_name = g_strdup ("MIT-MAGIC-COOKIE-1");
-
-    /* Check if they support our authorization */
-    for (j = packet->Request.authorization_names; *j; j++)
     {
-        if (strcmp (*j, authorization_name) == 0)
-        {
-             match_authorization = TRUE;
-             break;
-        }
+        if (strcmp (packet->Request.authentication_name, "") == 0)
+            decline_status = g_strdup_printf ("No matching authentication, server does not support unauthenticated connections");
+        else if (server->priv->key)
+            decline_status = g_strdup ("No matching authentication, server requires XDM-AUTHENTICATION-1");
+        else
+            decline_status = g_strdup ("No matching authentication, server only supports unauthenticated connections");
     }
 
-    /* Decline if don't support out authorization */
-    if (!match_authorization)
+    /* Choose an address to connect back on */
+    connection = choose_connection (packet, g_inet_socket_address_get_address (G_INET_SOCKET_ADDRESS (address)));
+    if (!connection && !decline_status)
+        decline_status = g_strdup ("No valid address found");
+
+    if (!authentication_name)
+        authentication_name = g_strdup ("");
+
+    /* Decline if request was not valid */
+    if (decline_status)
     {
         response = xdmcp_packet_alloc (XDMCP_Decline);
-        response->Decline.status = g_strdup_printf ("Server requires %s authorization", authorization_name);
-        g_free (authorization_name);
-        response->Decline.authentication_name = g_strdup (packet->Request.authentication_name);
+        response->Decline.status = decline_status;
+        response->Decline.authentication_name = authentication_name;
         response->Decline.authentication_data.data = authentication_data;
         response->Decline.authentication_data.length = authentication_data_length;
         send_packet (socket, address, response);
@@ -440,7 +539,7 @@ handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, X
         return;
     }
 
-    /* Perform requested authorization */
+    /* Generate authorization data */
     if (server->priv->key)
     {
         gint i;
@@ -461,6 +560,7 @@ handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, X
         XdmcpWrap (session_key, key, authorization_data, authorization_data_length);
 
         /* Authorization data is the number received from the client followed by the private session key */
+        authorization_name = g_strdup ("XDM-AUTHORIZATION-1");
         session_authorization_data = g_malloc (16);
         session_authorization_data_length = 16;
         XdmcpDecrementKey (&rho);
@@ -475,6 +575,7 @@ handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, X
         auth = x_authority_new_cookie (XAUTH_FAMILY_WILD, NULL, 0, "");
         authorization_data = x_authority_copy_authorization_data (auth);
         authorization_data_length = x_authority_get_authorization_data_length (auth);
+        authorization_name = g_strdup ("MIT-MAGIC-COOKIE-1");
         session_authorization_data = x_authority_copy_authorization_data (auth);
         session_authorization_data_length = x_authority_get_authorization_data_length (auth);
 
@@ -514,7 +615,7 @@ handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, X
 
     response = xdmcp_packet_alloc (XDMCP_Accept);
     response->Accept.session_id = xdmcp_session_get_id (session);
-    response->Accept.authentication_name = g_strdup (packet->Request.authentication_name);
+    response->Accept.authentication_name = authentication_name;
     response->Accept.authentication_data.data = authentication_data;
     response->Accept.authentication_data.length = authentication_data_length;
     response->Accept.authorization_name = authorization_name;
@@ -625,14 +726,23 @@ read_cb (GSocket *socket, GIOCondition condition, XDMCPServer *server)
         packet = xdmcp_packet_decode ((guint8 *)data, n_read);
         if (packet)
         {
-            g_debug ("Got %s", xdmcp_packet_tostring (packet));
+            gchar *packet_string, *address_string;
+
+            packet_string = xdmcp_packet_tostring (packet);
+            address_string = socket_address_to_string (address);
+            g_debug ("Got %s from %s", packet_string, address_string);
+            g_free (packet_string);
+            g_free (address_string);
 
             switch (packet->opcode)
             {
             case XDMCP_BroadcastQuery:
             case XDMCP_Query:
             case XDMCP_IndirectQuery:
-                handle_query (server, socket, address, packet);
+                handle_query (server, socket, address, packet->Query.authentication_names);
+                break;
+            case XDMCP_ForwardQuery:
+                handle_forward_query (server, socket, address, packet);
                 break;
             case XDMCP_Request:
                 handle_request (server, socket, address, packet);