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