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