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