a9418ec89509bc06ff8030377d0f17a7b0da0fee
[platform/upstream/glib.git] / gio / gdbusauth.c
1 /* GDBus - GLib D-Bus Library
2  *
3  * Copyright (C) 2008-2010 Red Hat, Inc.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General
16  * Public License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * Author: David Zeuthen <davidz@redhat.com>
21  */
22
23 #include "config.h"
24
25 #include "gdbusauth.h"
26
27 #include "gdbusauthmechanismanon.h"
28 #include "gdbusauthmechanismexternal.h"
29 #include "gdbusauthmechanismsha1.h"
30 #include "gdbusauthobserver.h"
31
32 #include "gdbuserror.h"
33 #include "gdbusutils.h"
34 #include "gioenumtypes.h"
35 #include "gcredentials.h"
36 #include "gdbusprivate.h"
37 #include "giostream.h"
38 #include "gdatainputstream.h"
39 #include "gdataoutputstream.h"
40
41 #ifdef G_OS_UNIX
42 #include "gnetworking.h"
43 #include "gunixconnection.h"
44 #include "gunixcredentialsmessage.h"
45 #endif
46
47 #include "glibintl.h"
48
49 G_GNUC_PRINTF(1, 2)
50 static void
51 debug_print (const gchar *message, ...)
52 {
53   if (G_UNLIKELY (_g_dbus_debug_authentication ()))
54     {
55       gchar *s;
56       GString *str;
57       va_list var_args;
58       guint n;
59
60       _g_dbus_debug_print_lock ();
61
62       va_start (var_args, message);
63       s = g_strdup_vprintf (message, var_args);
64       va_end (var_args);
65
66       str = g_string_new (NULL);
67       for (n = 0; s[n] != '\0'; n++)
68         {
69           if (G_UNLIKELY (s[n] == '\r'))
70             g_string_append (str, "\\r");
71           else if (G_UNLIKELY (s[n] == '\n'))
72             g_string_append (str, "\\n");
73           else
74             g_string_append_c (str, s[n]);
75         }
76       g_print ("GDBus-debug:Auth: %s\n", str->str);
77       g_string_free (str, TRUE);
78       g_free (s);
79
80       _g_dbus_debug_print_unlock ();
81     }
82 }
83
84 typedef struct
85 {
86   const gchar *name;
87   gint priority;
88   GType gtype;
89 } Mechanism;
90
91 static void mechanism_free (Mechanism *m);
92
93 struct _GDBusAuthPrivate
94 {
95   GIOStream *stream;
96
97   /* A list of available Mechanism, sorted according to priority  */
98   GList *available_mechanisms;
99 };
100
101 enum
102 {
103   PROP_0,
104   PROP_STREAM
105 };
106
107 G_DEFINE_TYPE_WITH_PRIVATE (GDBusAuth, _g_dbus_auth, G_TYPE_OBJECT)
108
109 /* ---------------------------------------------------------------------------------------------------- */
110
111 static void
112 _g_dbus_auth_finalize (GObject *object)
113 {
114   GDBusAuth *auth = G_DBUS_AUTH (object);
115
116   if (auth->priv->stream != NULL)
117     g_object_unref (auth->priv->stream);
118   g_list_free_full (auth->priv->available_mechanisms, (GDestroyNotify) mechanism_free);
119
120   if (G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize != NULL)
121     G_OBJECT_CLASS (_g_dbus_auth_parent_class)->finalize (object);
122 }
123
124 static void
125 _g_dbus_auth_get_property (GObject    *object,
126                            guint       prop_id,
127                            GValue     *value,
128                            GParamSpec *pspec)
129 {
130   GDBusAuth *auth = G_DBUS_AUTH (object);
131
132   switch (prop_id)
133     {
134     case PROP_STREAM:
135       g_value_set_object (value, auth->priv->stream);
136       break;
137
138     default:
139       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
140       break;
141     }
142 }
143
144 static void
145 _g_dbus_auth_set_property (GObject      *object,
146                            guint         prop_id,
147                            const GValue *value,
148                            GParamSpec   *pspec)
149 {
150   GDBusAuth *auth = G_DBUS_AUTH (object);
151
152   switch (prop_id)
153     {
154     case PROP_STREAM:
155       auth->priv->stream = g_value_dup_object (value);
156       break;
157
158     default:
159       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
160       break;
161     }
162 }
163
164 static void
165 _g_dbus_auth_class_init (GDBusAuthClass *klass)
166 {
167   GObjectClass *gobject_class;
168
169   gobject_class = G_OBJECT_CLASS (klass);
170   gobject_class->get_property = _g_dbus_auth_get_property;
171   gobject_class->set_property = _g_dbus_auth_set_property;
172   gobject_class->finalize     = _g_dbus_auth_finalize;
173
174   g_object_class_install_property (gobject_class,
175                                    PROP_STREAM,
176                                    g_param_spec_object ("stream",
177                                                         P_("IO Stream"),
178                                                         P_("The underlying GIOStream used for I/O"),
179                                                         G_TYPE_IO_STREAM,
180                                                         G_PARAM_READABLE |
181                                                         G_PARAM_WRITABLE |
182                                                         G_PARAM_CONSTRUCT_ONLY |
183                                                         G_PARAM_STATIC_NAME |
184                                                         G_PARAM_STATIC_BLURB |
185                                                         G_PARAM_STATIC_NICK));
186 }
187
188 static void
189 mechanism_free (Mechanism *m)
190 {
191   g_free (m);
192 }
193
194 static void
195 add_mechanism (GDBusAuth         *auth,
196                GDBusAuthObserver *observer,
197                GType              mechanism_type)
198 {
199   const gchar *name;
200
201   name = _g_dbus_auth_mechanism_get_name (mechanism_type);
202   if (observer == NULL || g_dbus_auth_observer_allow_mechanism (observer, name))
203     {
204       Mechanism *m;
205       m = g_new0 (Mechanism, 1);
206       m->name = name;
207       m->priority = _g_dbus_auth_mechanism_get_priority (mechanism_type);
208       m->gtype = mechanism_type;
209       auth->priv->available_mechanisms = g_list_prepend (auth->priv->available_mechanisms, m);
210     }
211 }
212
213 static gint
214 mech_compare_func (Mechanism *a, Mechanism *b)
215 {
216   gint ret;
217   /* ensure deterministic order */
218   ret = b->priority - a->priority;
219   if (ret == 0)
220     ret = g_strcmp0 (b->name, a->name);
221   return ret;
222 }
223
224 static void
225 _g_dbus_auth_init (GDBusAuth *auth)
226 {
227   auth->priv = _g_dbus_auth_get_instance_private (auth);
228 }
229
230 static void
231 _g_dbus_auth_add_mechs (GDBusAuth         *auth,
232                         GDBusAuthObserver *observer)
233 {
234   /* TODO: trawl extension points */
235   add_mechanism (auth, observer, G_TYPE_DBUS_AUTH_MECHANISM_ANON);
236   add_mechanism (auth, observer, G_TYPE_DBUS_AUTH_MECHANISM_SHA1);
237   add_mechanism (auth, observer, G_TYPE_DBUS_AUTH_MECHANISM_EXTERNAL);
238
239   auth->priv->available_mechanisms = g_list_sort (auth->priv->available_mechanisms,
240                                                   (GCompareFunc) mech_compare_func);
241 }
242
243 static GType
244 find_mech_by_name (GDBusAuth *auth,
245                    const gchar *name)
246 {
247   GType ret;
248   GList *l;
249
250   ret = (GType) 0;
251
252   for (l = auth->priv->available_mechanisms; l != NULL; l = l->next)
253     {
254       Mechanism *m = l->data;
255       if (g_strcmp0 (name, m->name) == 0)
256         {
257           ret = m->gtype;
258           goto out;
259         }
260     }
261
262  out:
263   return ret;
264 }
265
266 GDBusAuth  *
267 _g_dbus_auth_new (GIOStream *stream)
268 {
269   return g_object_new (G_TYPE_DBUS_AUTH,
270                        "stream", stream,
271                        NULL);
272 }
273
274 /* ---------------------------------------------------------------------------------------------------- */
275 /* like g_data_input_stream_read_line() but sets error if there's no content to read */
276 static gchar *
277 _my_g_data_input_stream_read_line (GDataInputStream  *dis,
278                                    gsize             *out_line_length,
279                                    GCancellable      *cancellable,
280                                    GError           **error)
281 {
282   gchar *ret;
283
284   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
285
286   ret = g_data_input_stream_read_line (dis,
287                                        out_line_length,
288                                        cancellable,
289                                        error);
290   if (ret == NULL && error != NULL && *error == NULL)
291     {
292       g_set_error_literal (error,
293                            G_IO_ERROR,
294                            G_IO_ERROR_FAILED,
295                            _("Unexpected lack of content trying to read a line"));
296     }
297
298   return ret;
299 }
300
301 /* This function is to avoid situations like this
302  *
303  * BEGIN\r\nl\0\0\1...
304  *
305  * e.g. where we read into the first D-Bus message while waiting for
306  * the final line from the client (TODO: file bug against gio for
307  * this)
308  */
309 static gchar *
310 _my_g_input_stream_read_line_safe (GInputStream  *i,
311                                    gsize         *out_line_length,
312                                    GCancellable  *cancellable,
313                                    GError       **error)
314 {
315   GString *str;
316   gchar c;
317   gssize num_read;
318   gboolean last_was_cr;
319
320   str = g_string_new (NULL);
321
322   last_was_cr = FALSE;
323   while (TRUE)
324     {
325       num_read = g_input_stream_read (i,
326                                       &c,
327                                       1,
328                                       cancellable,
329                                       error);
330       if (num_read == -1)
331         goto fail;
332       if (num_read == 0)
333         {
334           if (error != NULL && *error == NULL)
335             {
336               g_set_error_literal (error,
337                                    G_IO_ERROR,
338                                    G_IO_ERROR_FAILED,
339                                    _("Unexpected lack of content trying to (safely) read a line"));
340             }
341           goto fail;
342         }
343
344       g_string_append_c (str, (gint) c);
345       if (last_was_cr)
346         {
347           if (c == 0x0a)
348             {
349               g_assert (str->len >= 2);
350               g_string_set_size (str, str->len - 2);
351               goto out;
352             }
353         }
354       last_was_cr = (c == 0x0d);
355     }
356
357  out:
358   if (out_line_length != NULL)
359     *out_line_length = str->len;
360   return g_string_free (str, FALSE);
361
362  fail:
363   g_assert (error == NULL || *error != NULL);
364   g_string_free (str, TRUE);
365   return NULL;
366 }
367
368 /* ---------------------------------------------------------------------------------------------------- */
369
370 static void
371 append_nibble (GString *s, gint val)
372 {
373   g_string_append_c (s, val >= 10 ? ('a' + val - 10) : ('0' + val));
374 }
375
376 static gchar *
377 hexdecode (const gchar  *str,
378            gsize        *out_len,
379            GError      **error)
380 {
381   gchar *ret;
382   GString *s;
383   guint n;
384
385   ret = NULL;
386   s = g_string_new (NULL);
387
388   for (n = 0; str[n] != '\0'; n += 2)
389     {
390       gint upper_nibble;
391       gint lower_nibble;
392       guint value;
393
394       upper_nibble = g_ascii_xdigit_value (str[n]);
395       lower_nibble = g_ascii_xdigit_value (str[n + 1]);
396       if (upper_nibble == -1 || lower_nibble == -1)
397         {
398           g_set_error (error,
399                        G_IO_ERROR,
400                        G_IO_ERROR_FAILED,
401                        "Error hexdecoding string '%s' around position %d",
402                        str, n);
403           goto out;
404         }
405       value = (upper_nibble<<4) | lower_nibble;
406       g_string_append_c (s, value);
407     }
408
409   ret = g_string_free (s, FALSE);
410   s = NULL;
411
412  out:
413   if (s != NULL)
414     g_string_free (s, TRUE);
415   return ret;
416 }
417
418 /* TODO: take len */
419 static gchar *
420 hexencode (const gchar *str)
421 {
422   guint n;
423   GString *s;
424
425   s = g_string_new (NULL);
426   for (n = 0; str[n] != '\0'; n++)
427     {
428       gint val;
429       gint upper_nibble;
430       gint lower_nibble;
431
432       val = ((const guchar *) str)[n];
433       upper_nibble = val >> 4;
434       lower_nibble = val & 0x0f;
435
436       append_nibble (s, upper_nibble);
437       append_nibble (s, lower_nibble);
438     }
439
440   return g_string_free (s, FALSE);
441 }
442
443 /* ---------------------------------------------------------------------------------------------------- */
444
445 static GDBusAuthMechanism *
446 client_choose_mech_and_send_initial_response (GDBusAuth           *auth,
447                                               GCredentials        *credentials_that_were_sent,
448                                               const gchar* const  *supported_auth_mechs,
449                                               GPtrArray           *attempted_auth_mechs,
450                                               GDataOutputStream   *dos,
451                                               GCancellable        *cancellable,
452                                               GError             **error)
453 {
454   GDBusAuthMechanism *mech;
455   GType auth_mech_to_use_gtype;
456   guint n;
457   guint m;
458   gchar *initial_response;
459   gsize initial_response_len;
460   gchar *encoded;
461   gchar *s;
462
463  again:
464   mech = NULL;
465
466   debug_print ("CLIENT: Trying to choose mechanism");
467
468   /* find an authentication mechanism to try, if any */
469   auth_mech_to_use_gtype = (GType) 0;
470   for (n = 0; supported_auth_mechs[n] != NULL; n++)
471     {
472       gboolean attempted_already;
473       attempted_already = FALSE;
474       for (m = 0; m < attempted_auth_mechs->len; m++)
475         {
476           if (g_strcmp0 (supported_auth_mechs[n], attempted_auth_mechs->pdata[m]) == 0)
477             {
478               attempted_already = TRUE;
479               break;
480             }
481         }
482       if (!attempted_already)
483         {
484           auth_mech_to_use_gtype = find_mech_by_name (auth, supported_auth_mechs[n]);
485           if (auth_mech_to_use_gtype != (GType) 0)
486             break;
487         }
488     }
489
490   if (auth_mech_to_use_gtype == (GType) 0)
491     {
492       guint n;
493       gchar *available;
494       GString *tried_str;
495
496       debug_print ("CLIENT: Exhausted all available mechanisms");
497
498       available = g_strjoinv (", ", (gchar **) supported_auth_mechs);
499
500       tried_str = g_string_new (NULL);
501       for (n = 0; n < attempted_auth_mechs->len; n++)
502         {
503           if (n > 0)
504             g_string_append (tried_str, ", ");
505           g_string_append (tried_str, attempted_auth_mechs->pdata[n]);
506         }
507       g_set_error (error,
508                    G_IO_ERROR,
509                    G_IO_ERROR_FAILED,
510                    _("Exhausted all available authentication mechanisms (tried: %s) (available: %s)"),
511                    tried_str->str,
512                    available);
513       g_string_free (tried_str, TRUE);
514       g_free (available);
515       goto out;
516     }
517
518   /* OK, decided on a mechanism - let's do this thing */
519   mech = g_object_new (auth_mech_to_use_gtype,
520                        "stream", auth->priv->stream,
521                        "credentials", credentials_that_were_sent,
522                        NULL);
523   debug_print ("CLIENT: Trying mechanism '%s'", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
524   g_ptr_array_add (attempted_auth_mechs, (gpointer) _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
525
526   /* the auth mechanism may not be supported
527    * (for example, EXTERNAL only works if credentials were exchanged)
528    */
529   if (!_g_dbus_auth_mechanism_is_supported (mech))
530     {
531       debug_print ("CLIENT: Mechanism '%s' says it is not supported", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
532       g_object_unref (mech);
533       mech = NULL;
534       goto again;
535     }
536
537   initial_response_len = -1;
538   initial_response = _g_dbus_auth_mechanism_client_initiate (mech,
539                                                              &initial_response_len);
540 #if 0
541   g_printerr ("using auth mechanism with name '%s' of type '%s' with initial response '%s'\n",
542               _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype),
543               g_type_name (G_TYPE_FROM_INSTANCE (mech)),
544               initial_response);
545 #endif
546   if (initial_response != NULL)
547     {
548       //g_printerr ("initial_response = '%s'\n", initial_response);
549       encoded = hexencode (initial_response);
550       s = g_strdup_printf ("AUTH %s %s\r\n",
551                            _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype),
552                            encoded);
553       g_free (initial_response);
554       g_free (encoded);
555     }
556   else
557     {
558       s = g_strdup_printf ("AUTH %s\r\n", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype));
559     }
560   debug_print ("CLIENT: writing '%s'", s);
561   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
562     {
563       g_object_unref (mech);
564       mech = NULL;
565       g_free (s);
566       goto out;
567     }
568   g_free (s);
569
570  out:
571   return mech;
572 }
573
574
575 /* ---------------------------------------------------------------------------------------------------- */
576
577 typedef enum
578 {
579   CLIENT_STATE_WAITING_FOR_DATA,
580   CLIENT_STATE_WAITING_FOR_OK,
581   CLIENT_STATE_WAITING_FOR_REJECT,
582   CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD
583 } ClientState;
584
585 gchar *
586 _g_dbus_auth_run_client (GDBusAuth     *auth,
587                          GDBusAuthObserver     *observer,
588                          GDBusCapabilityFlags offered_capabilities,
589                          GDBusCapabilityFlags *out_negotiated_capabilities,
590                          GCancellable  *cancellable,
591                          GError       **error)
592 {
593   gchar *s;
594   GDataInputStream *dis;
595   GDataOutputStream *dos;
596   GCredentials *credentials;
597   gchar *ret_guid;
598   gchar *line;
599   gsize line_length;
600   gchar **supported_auth_mechs;
601   GPtrArray *attempted_auth_mechs;
602   GDBusAuthMechanism *mech;
603   ClientState state;
604   GDBusCapabilityFlags negotiated_capabilities;
605
606   debug_print ("CLIENT: initiating");
607
608   _g_dbus_auth_add_mechs (auth, observer);
609
610   ret_guid = NULL;
611   supported_auth_mechs = NULL;
612   attempted_auth_mechs = g_ptr_array_new ();
613   mech = NULL;
614   negotiated_capabilities = 0;
615   credentials = NULL;
616
617   dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream)));
618   dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream)));
619   g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (dis), FALSE);
620   g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (dos), FALSE);
621
622   g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
623
624 #ifdef G_OS_UNIX
625   if (G_IS_UNIX_CONNECTION (auth->priv->stream))
626     {
627       credentials = g_credentials_new ();
628       if (!g_unix_connection_send_credentials (G_UNIX_CONNECTION (auth->priv->stream),
629                                                cancellable,
630                                                error))
631         goto out;
632     }
633   else
634     {
635       if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error))
636         goto out;
637     }
638 #else
639   if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error))
640     goto out;
641 #endif
642
643   if (credentials != NULL)
644     {
645       if (G_UNLIKELY (_g_dbus_debug_authentication ()))
646         {
647           s = g_credentials_to_string (credentials);
648           debug_print ("CLIENT: sent credentials '%s'", s);
649           g_free (s);
650         }
651     }
652   else
653     {
654       debug_print ("CLIENT: didn't send any credentials");
655     }
656
657   /* TODO: to reduce roundtrips, try to pick an auth mechanism to start with */
658
659   /* Get list of supported authentication mechanisms */
660   s = "AUTH\r\n";
661   debug_print ("CLIENT: writing '%s'", s);
662   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
663     goto out;
664   state = CLIENT_STATE_WAITING_FOR_REJECT;
665
666   while (TRUE)
667     {
668       switch (state)
669         {
670         case CLIENT_STATE_WAITING_FOR_REJECT:
671           debug_print ("CLIENT: WaitingForReject");
672           line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
673           if (line == NULL)
674             goto out;
675           debug_print ("CLIENT: WaitingForReject, read '%s'", line);
676
677         choose_mechanism:
678           if (!g_str_has_prefix (line, "REJECTED "))
679             {
680               g_set_error (error,
681                            G_IO_ERROR,
682                            G_IO_ERROR_FAILED,
683                            "In WaitingForReject: Expected 'REJECTED am1 am2 ... amN', got '%s'",
684                            line);
685               g_free (line);
686               goto out;
687             }
688           if (supported_auth_mechs == NULL)
689             {
690               supported_auth_mechs = g_strsplit (line + sizeof ("REJECTED ") - 1, " ", 0);
691 #if 0
692               for (n = 0; supported_auth_mechs != NULL && supported_auth_mechs[n] != NULL; n++)
693                 g_printerr ("supported_auth_mechs[%d] = '%s'\n", n, supported_auth_mechs[n]);
694 #endif
695             }
696           g_free (line);
697           mech = client_choose_mech_and_send_initial_response (auth,
698                                                                credentials,
699                                                                (const gchar* const *) supported_auth_mechs,
700                                                                attempted_auth_mechs,
701                                                                dos,
702                                                                cancellable,
703                                                                error);
704           if (mech == NULL)
705             goto out;
706           if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA)
707             state = CLIENT_STATE_WAITING_FOR_DATA;
708           else
709             state = CLIENT_STATE_WAITING_FOR_OK;
710           break;
711
712         case CLIENT_STATE_WAITING_FOR_OK:
713           debug_print ("CLIENT: WaitingForOK");
714           line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
715           if (line == NULL)
716             goto out;
717           debug_print ("CLIENT: WaitingForOK, read '%s'", line);
718           if (g_str_has_prefix (line, "OK "))
719             {
720               if (!g_dbus_is_guid (line + 3))
721                 {
722                   g_set_error (error,
723                                G_IO_ERROR,
724                                G_IO_ERROR_FAILED,
725                                "Invalid OK response '%s'",
726                                line);
727                   g_free (line);
728                   goto out;
729                 }
730               ret_guid = g_strdup (line + 3);
731               g_free (line);
732
733               if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
734                 {
735                   s = "NEGOTIATE_UNIX_FD\r\n";
736                   debug_print ("CLIENT: writing '%s'", s);
737                   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
738                     goto out;
739                   state = CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD;
740                 }
741               else
742                 {
743                   s = "BEGIN\r\n";
744                   debug_print ("CLIENT: writing '%s'", s);
745                   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
746                     goto out;
747                   /* and we're done! */
748                   goto out;
749                 }
750             }
751           else if (g_str_has_prefix (line, "REJECTED "))
752             {
753               goto choose_mechanism;
754             }
755           else
756             {
757               /* TODO: handle other valid responses */
758               g_set_error (error,
759                            G_IO_ERROR,
760                            G_IO_ERROR_FAILED,
761                            "In WaitingForOk: unexpected response '%s'",
762                            line);
763               g_free (line);
764               goto out;
765             }
766           break;
767
768         case CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD:
769           debug_print ("CLIENT: WaitingForAgreeUnixFD");
770           line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
771           if (line == NULL)
772             goto out;
773           debug_print ("CLIENT: WaitingForAgreeUnixFD, read='%s'", line);
774           if (g_strcmp0 (line, "AGREE_UNIX_FD") == 0)
775             {
776               g_free (line);
777               negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
778               s = "BEGIN\r\n";
779               debug_print ("CLIENT: writing '%s'", s);
780               if (!g_data_output_stream_put_string (dos, s, cancellable, error))
781                 goto out;
782               /* and we're done! */
783               goto out;
784             }
785           else if (g_str_has_prefix (line, "ERROR") && (line[5] == 0 || g_ascii_isspace (line[5])))
786             {
787               //g_strstrip (line + 5); g_debug ("bah, no unix_fd: '%s'", line + 5);
788               g_free (line);
789               s = "BEGIN\r\n";
790               debug_print ("CLIENT: writing '%s'", s);
791               if (!g_data_output_stream_put_string (dos, s, cancellable, error))
792                 goto out;
793               /* and we're done! */
794               goto out;
795             }
796           else
797             {
798               /* TODO: handle other valid responses */
799               g_set_error (error,
800                            G_IO_ERROR,
801                            G_IO_ERROR_FAILED,
802                            "In WaitingForAgreeUnixFd: unexpected response '%s'",
803                            line);
804               g_free (line);
805               goto out;
806             }
807           break;
808
809         case CLIENT_STATE_WAITING_FOR_DATA:
810           debug_print ("CLIENT: WaitingForData");
811           line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
812           if (line == NULL)
813             goto out;
814           debug_print ("CLIENT: WaitingForData, read='%s'", line);
815           if (g_str_has_prefix (line, "DATA "))
816             {
817               gchar *encoded;
818               gchar *decoded_data;
819               gsize decoded_data_len = 0;
820
821               encoded = g_strdup (line + 5);
822               g_free (line);
823               g_strstrip (encoded);
824               decoded_data = hexdecode (encoded, &decoded_data_len, error);
825               g_free (encoded);
826               if (decoded_data == NULL)
827                 {
828                   g_prefix_error (error, "DATA response is malformed: ");
829                   /* invalid encoding, disconnect! */
830                   goto out;
831                 }
832               _g_dbus_auth_mechanism_client_data_receive (mech, decoded_data, decoded_data_len);
833               g_free (decoded_data);
834
835               if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND)
836                 {
837                   gchar *data;
838                   gsize data_len;
839                   gchar *encoded_data;
840                   data = _g_dbus_auth_mechanism_client_data_send (mech, &data_len);
841                   encoded_data = hexencode (data);
842                   s = g_strdup_printf ("DATA %s\r\n", encoded_data);
843                   g_free (encoded_data);
844                   g_free (data);
845                   debug_print ("CLIENT: writing '%s'", s);
846                   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
847                     {
848                       g_free (s);
849                       goto out;
850                     }
851                   g_free (s);
852                 }
853               state = CLIENT_STATE_WAITING_FOR_OK;
854             }
855           else if (g_str_has_prefix (line, "REJECTED "))
856             {
857               /* could be the chosen authentication method just doesn't work. Try
858                * another one...
859                */
860               goto choose_mechanism;
861             }
862           else
863             {
864               g_set_error (error,
865                            G_IO_ERROR,
866                            G_IO_ERROR_FAILED,
867                            "In WaitingForData: unexpected response '%s'",
868                            line);
869               g_free (line);
870               goto out;
871             }
872           break;
873
874         default:
875           g_assert_not_reached ();
876           break;
877         }
878
879     }; /* main authentication client loop */
880
881  out:
882   if (mech != NULL)
883     g_object_unref (mech);
884   g_ptr_array_unref (attempted_auth_mechs);
885   g_strfreev (supported_auth_mechs);
886   g_object_unref (dis);
887   g_object_unref (dos);
888
889   /* ensure return value is NULL if error is set */
890   if (error != NULL && *error != NULL)
891     {
892       g_free (ret_guid);
893       ret_guid = NULL;
894     }
895
896   if (ret_guid != NULL)
897     {
898       if (out_negotiated_capabilities != NULL)
899         *out_negotiated_capabilities = negotiated_capabilities;
900     }
901
902   if (credentials != NULL)
903     g_object_unref (credentials);
904
905   debug_print ("CLIENT: Done, authenticated=%d", ret_guid != NULL);
906
907   return ret_guid;
908 }
909
910 /* ---------------------------------------------------------------------------------------------------- */
911
912 static gchar *
913 get_auth_mechanisms (GDBusAuth     *auth,
914                      gboolean       allow_anonymous,
915                      const gchar   *prefix,
916                      const gchar   *suffix,
917                      const gchar   *separator)
918 {
919   GList *l;
920   GString *str;
921   gboolean need_sep;
922
923   str = g_string_new (prefix);
924   need_sep = FALSE;
925   for (l = auth->priv->available_mechanisms; l != NULL; l = l->next)
926     {
927       Mechanism *m = l->data;
928
929       if (!allow_anonymous && g_strcmp0 (m->name, "ANONYMOUS") == 0)
930         continue;
931
932       if (need_sep)
933         g_string_append (str, separator);
934       g_string_append (str, m->name);
935       need_sep = TRUE;
936     }
937
938   g_string_append (str, suffix);
939   return g_string_free (str, FALSE);
940 }
941
942
943 typedef enum
944 {
945   SERVER_STATE_WAITING_FOR_AUTH,
946   SERVER_STATE_WAITING_FOR_DATA,
947   SERVER_STATE_WAITING_FOR_BEGIN
948 } ServerState;
949
950 gboolean
951 _g_dbus_auth_run_server (GDBusAuth              *auth,
952                          GDBusAuthObserver      *observer,
953                          const gchar            *guid,
954                          gboolean                allow_anonymous,
955                          GDBusCapabilityFlags    offered_capabilities,
956                          GDBusCapabilityFlags   *out_negotiated_capabilities,
957                          GCredentials          **out_received_credentials,
958                          GCancellable           *cancellable,
959                          GError                **error)
960 {
961   gboolean ret;
962   ServerState state;
963   GDataInputStream *dis;
964   GDataOutputStream *dos;
965   GError *local_error;
966   guchar byte;
967   gchar *line;
968   gsize line_length;
969   GDBusAuthMechanism *mech;
970   gchar *s;
971   GDBusCapabilityFlags negotiated_capabilities;
972   GCredentials *credentials;
973
974   debug_print ("SERVER: initiating");
975
976   _g_dbus_auth_add_mechs (auth, observer);
977
978   ret = FALSE;
979   dis = NULL;
980   dos = NULL;
981   mech = NULL;
982   negotiated_capabilities = 0;
983   credentials = NULL;
984
985   if (!g_dbus_is_guid (guid))
986     {
987       g_set_error (error,
988                    G_IO_ERROR,
989                    G_IO_ERROR_FAILED,
990                    "The given guid '%s' is not valid",
991                    guid);
992       goto out;
993     }
994
995   dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream)));
996   dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream)));
997   g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (dis), FALSE);
998   g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (dos), FALSE);
999
1000   g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
1001
1002   /* first read the NUL-byte (TODO: read credentials if using a unix domain socket) */
1003 #ifdef G_OS_UNIX
1004   if (G_IS_UNIX_CONNECTION (auth->priv->stream))
1005     {
1006       local_error = NULL;
1007       credentials = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (auth->priv->stream),
1008                                                            cancellable,
1009                                                            &local_error);
1010       if (credentials == NULL && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED))
1011         {
1012           g_propagate_error (error, local_error);
1013           goto out;
1014         }
1015     }
1016   else
1017     {
1018       local_error = NULL;
1019       byte = g_data_input_stream_read_byte (dis, cancellable, &local_error);
1020       byte = byte; /* To avoid -Wunused-but-set-variable */
1021       if (local_error != NULL)
1022         {
1023           g_propagate_error (error, local_error);
1024           goto out;
1025         }
1026     }
1027 #else
1028   local_error = NULL;
1029   byte = g_data_input_stream_read_byte (dis, cancellable, &local_error);
1030   byte = byte; /* To avoid -Wunused-but-set-variable */
1031   if (local_error != NULL)
1032     {
1033       g_propagate_error (error, local_error);
1034       goto out;
1035     }
1036 #endif
1037   if (credentials != NULL)
1038     {
1039       if (G_UNLIKELY (_g_dbus_debug_authentication ()))
1040         {
1041           s = g_credentials_to_string (credentials);
1042           debug_print ("SERVER: received credentials '%s'", s);
1043           g_free (s);
1044         }
1045     }
1046   else
1047     {
1048       debug_print ("SERVER: didn't receive any credentials");
1049     }
1050
1051   state = SERVER_STATE_WAITING_FOR_AUTH;
1052   while (TRUE)
1053     {
1054       switch (state)
1055         {
1056         case SERVER_STATE_WAITING_FOR_AUTH:
1057           debug_print ("SERVER: WaitingForAuth");
1058           line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
1059           debug_print ("SERVER: WaitingForAuth, read '%s'", line);
1060           if (line == NULL)
1061             goto out;
1062           if (g_strcmp0 (line, "AUTH") == 0)
1063             {
1064               s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
1065               debug_print ("SERVER: writing '%s'", s);
1066               if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1067                 {
1068                   g_free (s);
1069                   goto out;
1070                 }
1071               g_free (s);
1072               g_free (line);
1073             }
1074           else if (g_str_has_prefix (line, "AUTH "))
1075             {
1076               gchar **tokens;
1077               const gchar *encoded;
1078               const gchar *mech_name;
1079               GType auth_mech_to_use_gtype;
1080
1081               tokens = g_strsplit (line, " ", 0);
1082               g_free (line);
1083
1084               switch (g_strv_length (tokens))
1085                 {
1086                 case 2:
1087                   /* no initial response */
1088                   mech_name = tokens[1];
1089                   encoded = NULL;
1090                   break;
1091
1092                 case 3:
1093                   /* initial response */
1094                   mech_name = tokens[1];
1095                   encoded = tokens[2];
1096                   break;
1097
1098                 default:
1099                   g_set_error (error,
1100                                G_IO_ERROR,
1101                                G_IO_ERROR_FAILED,
1102                                "Unexpected line '%s' while in WaitingForAuth state",
1103                                line);
1104                   g_strfreev (tokens);
1105                   goto out;
1106                 }
1107
1108               /* TODO: record that the client has attempted to use this mechanism */
1109               //g_debug ("client is trying '%s'", mech_name);
1110
1111               auth_mech_to_use_gtype = find_mech_by_name (auth, mech_name);
1112               if ((auth_mech_to_use_gtype == (GType) 0) ||
1113                   (!allow_anonymous && g_strcmp0 (mech_name, "ANONYMOUS") == 0))
1114                 {
1115                   /* We don't support this auth mechanism */
1116                   g_strfreev (tokens);
1117                   s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
1118                   debug_print ("SERVER: writing '%s'", s);
1119                   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1120                     {
1121                       g_free (s);
1122                       goto out;
1123                     }
1124                   g_free (s);
1125
1126                   /* stay in WAITING FOR AUTH */
1127                   state = SERVER_STATE_WAITING_FOR_AUTH;
1128                 }
1129               else
1130                 {
1131                   gchar *initial_response;
1132                   gsize initial_response_len;
1133
1134                   mech = g_object_new (auth_mech_to_use_gtype,
1135                                        "stream", auth->priv->stream,
1136                                        "credentials", credentials,
1137                                        NULL);
1138
1139                   initial_response = NULL;
1140                   initial_response_len = 0;
1141                   if (encoded != NULL)
1142                     {
1143                       initial_response = hexdecode (encoded, &initial_response_len, error);
1144                       if (initial_response == NULL)
1145                         {
1146                           g_prefix_error (error, "Initial response is malformed: ");
1147                           /* invalid encoding, disconnect! */
1148                           g_strfreev (tokens);
1149                           goto out;
1150                         }
1151                     }
1152
1153                   _g_dbus_auth_mechanism_server_initiate (mech,
1154                                                           initial_response,
1155                                                           initial_response_len);
1156                   g_free (initial_response);
1157                   g_strfreev (tokens);
1158
1159                 change_state:
1160                   switch (_g_dbus_auth_mechanism_server_get_state (mech))
1161                     {
1162                     case G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED:
1163                       if (observer != NULL &&
1164                           !g_dbus_auth_observer_authorize_authenticated_peer (observer,
1165                                                                               auth->priv->stream,
1166                                                                               credentials))
1167                         {
1168                           /* disconnect */
1169                           g_set_error_literal (error,
1170                                                G_IO_ERROR,
1171                                                G_IO_ERROR_FAILED,
1172                                                _("Cancelled via GDBusAuthObserver::authorize-authenticated-peer"));
1173                           goto out;
1174                         }
1175                       else
1176                         {
1177                           s = g_strdup_printf ("OK %s\r\n", guid);
1178                           debug_print ("SERVER: writing '%s'", s);
1179                           if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1180                             {
1181                               g_free (s);
1182                               goto out;
1183                             }
1184                           g_free (s);
1185                           state = SERVER_STATE_WAITING_FOR_BEGIN;
1186                         }
1187                       break;
1188
1189                     case G_DBUS_AUTH_MECHANISM_STATE_REJECTED:
1190                       s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " ");
1191                       debug_print ("SERVER: writing '%s'", s);
1192                       if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1193                         {
1194                           g_free (s);
1195                           goto out;
1196                         }
1197                       g_free (s);
1198                       state = SERVER_STATE_WAITING_FOR_AUTH;
1199                       break;
1200
1201                     case G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA:
1202                       state = SERVER_STATE_WAITING_FOR_DATA;
1203                       break;
1204
1205                     case G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND:
1206                       {
1207                         gchar *data;
1208                         gsize data_len;
1209                         gchar *encoded_data;
1210                         data = _g_dbus_auth_mechanism_server_data_send (mech, &data_len);
1211                         encoded_data = hexencode (data);
1212                         s = g_strdup_printf ("DATA %s\r\n", encoded_data);
1213                         g_free (encoded_data);
1214                         g_free (data);
1215                         debug_print ("SERVER: writing '%s'", s);
1216                         if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1217                           {
1218                             g_free (s);
1219                             goto out;
1220                           }
1221                         g_free (s);
1222                       }
1223                       goto change_state;
1224                       break;
1225
1226                     default:
1227                       /* TODO */
1228                       g_assert_not_reached ();
1229                       break;
1230                     }
1231                 }
1232             }
1233           else
1234             {
1235               g_set_error (error,
1236                            G_IO_ERROR,
1237                            G_IO_ERROR_FAILED,
1238                            "Unexpected line '%s' while in WaitingForAuth state",
1239                            line);
1240               g_free (line);
1241               goto out;
1242             }
1243           break;
1244
1245         case SERVER_STATE_WAITING_FOR_DATA:
1246           debug_print ("SERVER: WaitingForData");
1247           line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error);
1248           debug_print ("SERVER: WaitingForData, read '%s'", line);
1249           if (line == NULL)
1250             goto out;
1251           if (g_str_has_prefix (line, "DATA "))
1252             {
1253               gchar *encoded;
1254               gchar *decoded_data;
1255               gsize decoded_data_len = 0;
1256
1257               encoded = g_strdup (line + 5);
1258               g_free (line);
1259               g_strstrip (encoded);
1260               decoded_data = hexdecode (encoded, &decoded_data_len, error);
1261               g_free (encoded);
1262               if (decoded_data == NULL)
1263                 {
1264                   g_prefix_error (error, "DATA response is malformed: ");
1265                   /* invalid encoding, disconnect! */
1266                   goto out;
1267                 }
1268               _g_dbus_auth_mechanism_server_data_receive (mech, decoded_data, decoded_data_len);
1269               g_free (decoded_data);
1270               /* oh man, this goto-crap is so ugly.. really need to rewrite the state machine */
1271               goto change_state;
1272             }
1273           else
1274             {
1275               g_set_error (error,
1276                            G_IO_ERROR,
1277                            G_IO_ERROR_FAILED,
1278                            "Unexpected line '%s' while in WaitingForData state",
1279                            line);
1280               g_free (line);
1281             }
1282           goto out;
1283
1284         case SERVER_STATE_WAITING_FOR_BEGIN:
1285           debug_print ("SERVER: WaitingForBegin");
1286           /* Use extremely slow (but reliable) line reader - this basically
1287            * does a recvfrom() system call per character
1288            *
1289            * (the problem with using GDataInputStream's read_line is that because of
1290            * buffering it might start reading into the first D-Bus message that
1291            * appears after "BEGIN\r\n"....)
1292            */
1293           line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream),
1294                                                     &line_length,
1295                                                     cancellable,
1296                                                     error);
1297           debug_print ("SERVER: WaitingForBegin, read '%s'", line);
1298           if (line == NULL)
1299             goto out;
1300           if (g_strcmp0 (line, "BEGIN") == 0)
1301             {
1302               /* YAY, done! */
1303               ret = TRUE;
1304               g_free (line);
1305               goto out;
1306             }
1307           else if (g_strcmp0 (line, "NEGOTIATE_UNIX_FD") == 0)
1308             {
1309               g_free (line);
1310               if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING)
1311                 {
1312                   negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING;
1313                   s = "AGREE_UNIX_FD\r\n";
1314                   debug_print ("SERVER: writing '%s'", s);
1315                   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1316                     goto out;
1317                 }
1318               else
1319                 {
1320                   s = "ERROR \"fd passing not offered\"\r\n";
1321                   debug_print ("SERVER: writing '%s'", s);
1322                   if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1323                     goto out;
1324                 }
1325             }
1326           else
1327             {
1328               g_debug ("Unexpected line '%s' while in WaitingForBegin state", line);
1329               g_free (line);
1330               s = "ERROR \"Unknown Command\"\r\n";
1331               debug_print ("SERVER: writing '%s'", s);
1332               if (!g_data_output_stream_put_string (dos, s, cancellable, error))
1333                 goto out;
1334             }
1335           break;
1336
1337         default:
1338           g_assert_not_reached ();
1339           break;
1340         }
1341     }
1342
1343
1344   g_set_error_literal (error,
1345                        G_IO_ERROR,
1346                        G_IO_ERROR_FAILED,
1347                        "Not implemented (server)");
1348
1349  out:
1350   if (mech != NULL)
1351     g_object_unref (mech);
1352   if (dis != NULL)
1353     g_object_unref (dis);
1354   if (dos != NULL)
1355     g_object_unref (dos);
1356
1357   /* ensure return value is FALSE if error is set */
1358   if (error != NULL && *error != NULL)
1359     {
1360       ret = FALSE;
1361     }
1362
1363   if (ret)
1364     {
1365       if (out_negotiated_capabilities != NULL)
1366         *out_negotiated_capabilities = negotiated_capabilities;
1367       if (out_received_credentials != NULL)
1368         *out_received_credentials = credentials != NULL ? g_object_ref (credentials) : NULL;
1369     }
1370
1371   if (credentials != NULL)
1372     g_object_unref (credentials);
1373
1374   debug_print ("SERVER: Done, authenticated=%d", ret);
1375
1376   return ret;
1377 }
1378
1379 /* ---------------------------------------------------------------------------------------------------- */