]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blob - src/xdmcp-server.c
Make --test-mode which runs as the current user
[sojka/lightdm.git] / src / xdmcp-server.c
1 /*
2  * Copyright (C) 2010 Robert Ancell.
3  * Author: Robert Ancell <robert.ancell@canonical.com>
4  * 
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
9  * license.
10  */
11
12 #include <stdlib.h>
13 #include <string.h>
14 #include <X11/X.h>
15 #define HASXDMAUTH
16 #include <X11/Xdmcp.h>
17 #include <gio/gio.h>
18
19 #include "xdmcp-server.h"
20 #include "xdmcp-protocol.h"
21 #include "xdmcp-session-private.h"
22
23 enum {
24     PROP_0,
25     PROP_PORT,  
26     PROP_HOSTNAME,
27     PROP_STATUS,
28     PROP_AUTHENTICATION_KEY
29 };
30
31 enum {
32     SESSION_ADDED,
33     LAST_SIGNAL
34 };
35 static guint signals[LAST_SIGNAL] = { 0 };
36
37 struct XDMCPServerPrivate
38 {
39     GKeyFile *config;
40   
41     guint port;
42
43     GSocket *socket;
44
45     gchar *hostname;
46
47     gchar *status;
48
49     gchar *authentication_key_string;
50
51     GHashTable *sessions;
52 };
53
54 G_DEFINE_TYPE (XDMCPServer, xdmcp_server, G_TYPE_OBJECT);
55
56 XDMCPServer *
57 xdmcp_server_new (void)
58 {
59     return g_object_new (XDMCP_SERVER_TYPE, NULL);
60 }
61
62 void
63 xdmcp_server_set_port (XDMCPServer *server, guint port)
64 {
65     server->priv->port = port;
66 }
67
68 guint
69 xdmcp_server_get_port (XDMCPServer *server)
70 {
71     return server->priv->port;
72 }
73
74 void
75 xdmcp_server_set_hostname (XDMCPServer *server, const gchar *hostname)
76 {
77     g_free (server->priv->hostname);
78     server->priv->hostname = g_strdup (hostname);
79 }
80
81 const gchar *
82 xdmcp_server_get_hostname (XDMCPServer *server)
83 {
84     return server->priv->hostname;
85 }
86
87 void
88 xdmcp_server_set_status (XDMCPServer *server, const gchar *status)
89 {
90     g_free (server->priv->status);
91     server->priv->status = g_strdup (status);
92 }
93
94 const gchar *
95 xdmcp_server_get_status (XDMCPServer *server)
96 {
97     return server->priv->status;
98 }
99
100 void
101 xdmcp_server_set_authentication_key (XDMCPServer *server, const gchar *key_string)
102 {
103     g_free (server->priv->authentication_key_string);
104     server->priv->authentication_key_string = g_strdup (key_string);
105 }
106
107 const gchar *
108 xdmcp_server_get_authentication_key (XDMCPServer *server)
109 {
110     return server->priv->authentication_key_string;
111 }
112
113 static XDMCPSession *
114 add_session (XDMCPServer *server)
115 {
116     XDMCPSession *session;
117     guint16 id;
118
119     do
120     {
121         id = g_random_int () & 0xFFFFFFFF;
122     } while (g_hash_table_lookup (server->priv->sessions, GINT_TO_POINTER ((gint) id)));
123
124     session = xdmcp_session_new (id);
125     g_hash_table_insert (server->priv->sessions, GINT_TO_POINTER ((gint) id), g_object_ref (session));
126
127     return session;
128 }
129
130 static XDMCPSession *
131 get_session (XDMCPServer *server, guint16 id)
132 {
133     return g_hash_table_lookup (server->priv->sessions, GINT_TO_POINTER ((gint) id));
134 }
135
136 static void
137 send_packet (GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
138 {
139     guchar data[1024];
140     gssize n_written;
141
142     g_debug ("Send %s", xdmcp_packet_tostring (packet));
143               
144     n_written = xdmcp_packet_encode (packet, data, 1024);
145     if (n_written < 0)
146       g_critical ("Failed to encode XDMCP packet");
147     else
148     {
149         GError *error = NULL;
150
151         if (g_socket_send_to (socket, address, (gchar *) data, n_written, NULL, &error) < 0)
152             g_warning ("Error sending packet: %s", error->message);
153
154         g_clear_error (&error);
155     }
156 }
157
158 static void
159 handle_query (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
160 {
161     XDMCPPacket *response;
162     const gchar *authentication_name = "";
163     gchar **i;
164
165     /* Use authentication if we are configured for it */
166     for (i = packet->Query.authentication_names; *i; i++)
167     {
168         if (strcmp (*i, "XDM-AUTHENTICATION-1") == 0)
169         {
170             if (server->priv->authentication_key_string)
171                 authentication_name = "XDM-AUTHENTICATION-1";
172         }
173     }
174
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);
179
180     send_packet (socket, address, response);
181
182     xdmcp_packet_free (response);
183 }
184
185 static guchar
186 atox (gchar c)
187 {
188     if (c >= '0' && c <= '9')
189         return c - '0';
190     if (c >= 'a' && c <= 'f')
191         return c - 'a' + 10;
192     if (c >= 'A' && c <= 'F')
193         return c - 'A' + 10;
194     return 0;
195 }
196
197 static void
198 string_to_key (XdmAuthKeyRec *key, const gchar *string)
199 {
200     gint i;
201   
202     if (strncmp (string, "0x", 2) == 0 || strncmp (string, "0X", 2) == 0)
203     {
204         gint j = 0;
205
206         for (i = 0; i < 8 && string[j]; i++)
207         {
208             key->data[i] = 0;
209
210             if (string[j])
211             {
212                 key->data[i] |= atox (string[j]) >> 8;
213                 j++;
214                 if (string[j])
215                 {
216                     key->data[i] |= atox (string[j+1]);
217                     j++;
218                 }
219             }
220         }
221     }
222     else
223     {
224         key->data[0] = 0;
225         for (i = 1; i < 8 && string[i-1]; i++)
226             key->data[i] = string[i-1];
227         for (; i < 8; i++)
228             key->data[i] = 0;
229     }
230 }
231
232 static void
233 handle_request (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
234 {
235     int i;
236     XDMCPPacket *response;
237     XDMCPSession *session;
238     const gchar *authorization_name = "";
239     guchar *authentication_data = NULL;
240     gsize authentication_data_length = 0;
241     gchar **j;
242     GInetAddress *address4 = NULL; /*, *address6 = NULL;*/
243   
244     /* FIXME: If session not started (i.e. not received the Manage then response with Accept again) */
245
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)
250     {
251         XdmAuthKeyRec key, message;
252
253         if (!server->priv->authentication_key_string)
254         {
255             // FIXME: Send Decline
256             return;
257         }
258
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)
261         {
262             // FIXME: Send Decline
263             return;
264         }
265
266         /* Setup key */
267         string_to_key (&key, server->priv->authentication_key_string);
268
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;
272
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);
276
277         //authorization_name = "XDM_AUTHORIZATION-1";
278     }
279     else
280     {
281         // FIXME: Send Decline
282         return;
283     }
284
285     /* Choose an authorization from the list */
286     for (j = packet->Request.authorization_names; *j; j++)
287     {
288         if (strcmp (*j, "MIT-MAGIC-COOKIE-1") == 0)
289         {
290             // FIXME: Generate cookie
291         }
292         else if (strcmp (*j, "XDM-AUTHORIZATION-1") == 0)
293         {
294         }
295     }
296
297     // FIXME: Check have supported authorization
298
299     for (i = 0; i < packet->Request.n_connections; i++)
300     {
301         XDMCPConnection *connection;
302
303         connection = &packet->Request.connections[i];
304         switch (connection->type)
305         {
306         case FamilyInternet:
307             if (connection->address.length == 4)
308                 address4 = g_inet_address_new_from_bytes (connection->address.data, G_SOCKET_FAMILY_IPV4);
309             break;
310         /*case FamilyInternet6:
311             if (connection->address.length == 16)
312                 address6 = g_inet_address_new_from_bytes (connection->address.data, G_SOCKET_FAMILY_IPV6);          
313             break;*/
314         }
315     }
316
317     if (!address4)
318     {
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);
326         return;
327     }
328
329     /* FIXME: Allow a higher layer to decline */
330
331     session = add_session (server);
332     session->priv->address = address4; /*address6 ? address6 : address4;*/
333     // FIXME: Timeout inactive sessions?
334
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);
343 }
344
345 static void
346 handle_manage (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
347 {
348     XDMCPSession *session;
349
350     session = get_session (server, packet->Manage.session_id);
351     if (session)
352     {
353         gchar *ip_address, *display_address;
354
355         /* Ignore duplicate requests */
356         if (session->priv->started)
357         {
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");
361             return;
362         }
363
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);
367         g_free (ip_address);
368         session->priv->connection = xcb_connect (display_address, NULL);
369         // TODO: xcb_connect_to_display_with_auth_info (display_address, NULL);
370       
371         if (!session->priv->connection)
372         {
373             XDMCPPacket *response;
374
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);
380         }
381         else
382         {
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);
387         }
388
389         g_free (display_address);
390     }
391     else
392     {
393         XDMCPPacket *response;
394
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);
399     }
400 }
401
402 static void
403 handle_keep_alive (XDMCPServer *server, GSocket *socket, GSocketAddress *address, XDMCPPacket *packet)
404 {
405     XDMCPPacket *response;
406     XDMCPSession *session;
407     gboolean alive = FALSE;
408
409     session = get_session (server, packet->KeepAlive.session_id);
410     if (session)
411         alive = TRUE; //xdmcp_session_get_alive (session);
412
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);
418 }
419
420 static gboolean
421 read_cb (GSocket *socket, GIOCondition condition, XDMCPServer *server)
422 {
423     GSocketAddress *address;
424     gchar data[1024];
425     GError *error = NULL;
426     gssize n_read;
427
428     n_read = g_socket_receive_from (socket, &address, data, 1024, NULL, &error);
429     if (n_read > 0)
430     {
431         XDMCPPacket *packet;
432
433         packet = xdmcp_packet_decode ((guchar *)data, n_read);
434         if (packet)
435         {        
436             g_debug ("Got %s", xdmcp_packet_tostring (packet));
437
438             switch (packet->opcode)
439             {
440             case XDMCP_BroadcastQuery:
441             case XDMCP_Query:
442             case XDMCP_IndirectQuery:
443                 handle_query (server, socket, address, packet);
444                 break;
445             case XDMCP_Request:
446                 handle_request (server, socket, address, packet);
447                 break;
448             case XDMCP_Manage:
449                 handle_manage (server, socket, address, packet);              
450                 break;
451             case XDMCP_KeepAlive:
452                 handle_keep_alive (server, socket, address, packet);
453                 break;
454             default:
455                 g_warning ("Got unexpected XDMCP packet %d", packet->opcode);
456                 break;
457             }
458
459             xdmcp_packet_free (packet);
460         }
461     }
462     else
463         g_warning ("Failed to read from XDMCP socket: %s", error->message);
464
465     g_clear_error (&error);
466
467     return TRUE;
468 }
469
470 gboolean
471 xdmcp_server_start (XDMCPServer *server)
472 {
473     GSocketAddress *address;
474     GSource *source;
475     gboolean result;
476     GError *error = NULL;
477   
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)
483         return FALSE;
484
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);
487     if (!result)
488         g_warning ("Failed to bind XDMCP server port: %s", error->message);
489     g_clear_error (&error);
490     if (!result)
491         return FALSE;
492
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);
496
497     return TRUE;
498 }
499
500 static void
501 xdmcp_server_init (XDMCPServer *server)
502 {
503     server->priv = G_TYPE_INSTANCE_GET_PRIVATE (server, XDMCP_SERVER_TYPE, XDMCPServerPrivate);
504
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);
509 }
510
511 static void
512 xdmcp_server_set_property (GObject      *object,
513                            guint         prop_id,
514                            const GValue *value,
515                            GParamSpec   *pspec)
516 {
517     XDMCPServer *self;
518
519     self = XDMCP_SERVER (object);
520
521     switch (prop_id) {
522     case PROP_HOSTNAME:
523         xdmcp_server_set_hostname (self, g_value_get_string (value));
524         break;
525     case PROP_STATUS:
526         xdmcp_server_set_status (self, g_value_get_string (value));      
527         break;
528     case PROP_AUTHENTICATION_KEY:
529         xdmcp_server_set_authentication_key (self, g_value_get_string (value));      
530         break;
531     default:
532         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
533         break;
534     }
535 }
536
537 static void
538 xdmcp_server_get_property (GObject    *object,
539                            guint       prop_id,
540                            GValue     *value,
541                            GParamSpec *pspec)
542 {
543     XDMCPServer *self;
544
545     self = XDMCP_SERVER (object);
546
547     switch (prop_id) {
548     case PROP_HOSTNAME:
549         g_value_set_string (value, self->priv->hostname);
550         break;
551     case PROP_STATUS:
552         g_value_set_string (value, self->priv->status);
553         break;
554     case PROP_AUTHENTICATION_KEY:
555         g_value_set_string (value, xdmcp_server_get_authentication_key (self));
556         break;
557     default:
558         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
559         break;
560     }
561 }
562
563 static void
564 xdmcp_server_finalize (GObject *object)
565 {
566     XDMCPServer *self;
567
568     self = XDMCP_SERVER (object);
569   
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);
576 }
577
578 static void
579 xdmcp_server_class_init (XDMCPServerClass *klass)
580 {
581     GObjectClass *object_class = G_OBJECT_CLASS (klass);
582
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;  
586
587     g_type_class_add_private (klass, sizeof (XDMCPServerPrivate));
588
589     g_object_class_install_property (object_class,
590                                      PROP_PORT,
591                                      g_param_spec_int ("port",
592                                                        "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,
597                                      PROP_HOSTNAME,
598                                      g_param_spec_string ("hostname",
599                                                           "hostname",
600                                                           "Hostname",
601                                                           NULL,
602                                                           G_PARAM_READWRITE));
603     g_object_class_install_property (object_class,
604                                      PROP_STATUS,
605                                      g_param_spec_string ("status",
606                                                           "status",
607                                                           "Server status",
608                                                           NULL,
609                                                           G_PARAM_READWRITE));
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",
615                                                           NULL,
616                                                           G_PARAM_READWRITE));
617
618     signals[SESSION_ADDED] =
619         g_signal_new ("session-added",
620                       G_TYPE_FROM_CLASS (klass),
621                       G_SIGNAL_RUN_LAST,
622                       G_STRUCT_OFFSET (XDMCPServerClass, session_added),
623                       NULL, NULL,
624                       g_cclosure_marshal_VOID__OBJECT,
625                       G_TYPE_NONE, 1, XDMCP_SESSION_TYPE);
626 }