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