/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 | | * This library is free software; you can redistribute it and/or |
6 | | * modify it under the terms of the GNU Lesser General Public |
7 | | * License as published by the Free Software Foundation; either |
8 | | * version 2.1 of the License, or (at your option) any later version. |
9 | | * |
10 | | * This library is distributed in the hope that it will be useful, |
11 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | | * Lesser General Public License for more details. |
14 | | * |
15 | | * You should have received a copy of the GNU Lesser General |
16 | | * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. |
17 | | * |
18 | | * Author: David Zeuthen <davidz@redhat.com> |
19 | | */ |
20 | | |
21 | | #include "config.h" |
22 | | |
23 | | #include "gdbusauth.h" |
24 | | |
25 | | #include "gdbusauthmechanismanon.h" |
26 | | #include "gdbusauthmechanismexternal.h" |
27 | | #include "gdbusauthmechanismsha1.h" |
28 | | #include "gdbusauthobserver.h" |
29 | | |
30 | | #include "gdbuserror.h" |
31 | | #include "gdbusutils.h" |
32 | | #include "gioenumtypes.h" |
33 | | #include "gcredentials.h" |
34 | | #include "gcredentialsprivate.h" |
35 | | #include "gdbusprivate.h" |
36 | | #include "giostream.h" |
37 | | #include "gdatainputstream.h" |
38 | | #include "gdataoutputstream.h" |
39 | | |
40 | | #ifdef G_OS_UNIX |
41 | | #include "gnetworking.h" |
42 | | #include "gunixconnection.h" |
43 | | #include "gunixcredentialsmessage.h" |
44 | | #endif |
45 | | |
46 | | #include "glibintl.h" |
47 | | |
48 | | G_GNUC_PRINTF(1, 2) |
49 | | static void |
50 | | debug_print (const gchar *message, ...) |
51 | 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 | | const gchar* const *supported_auth_mechs, |
421 | | GPtrArray *attempted_auth_mechs, |
422 | | GDataOutputStream *dos, |
423 | | GCancellable *cancellable, |
424 | | GError **error) |
425 | 0 | { |
426 | 0 | GDBusAuthMechanism *mech; |
427 | 0 | GType auth_mech_to_use_gtype; |
428 | 0 | guint n; |
429 | 0 | guint m; |
430 | 0 | gchar *initial_response; |
431 | 0 | gsize initial_response_len; |
432 | 0 | gchar *encoded; |
433 | 0 | gchar *s; |
434 | |
|
435 | 0 | again: |
436 | 0 | mech = NULL; |
437 | |
|
438 | 0 | debug_print ("CLIENT: Trying to choose mechanism"); |
439 | | |
440 | | /* find an authentication mechanism to try, if any */ |
441 | 0 | auth_mech_to_use_gtype = (GType) 0; |
442 | 0 | for (n = 0; supported_auth_mechs[n] != NULL; n++) |
443 | 0 | { |
444 | 0 | gboolean attempted_already; |
445 | 0 | attempted_already = FALSE; |
446 | 0 | for (m = 0; m < attempted_auth_mechs->len; m++) |
447 | 0 | { |
448 | 0 | if (g_strcmp0 (supported_auth_mechs[n], attempted_auth_mechs->pdata[m]) == 0) |
449 | 0 | { |
450 | 0 | attempted_already = TRUE; |
451 | 0 | break; |
452 | 0 | } |
453 | 0 | } |
454 | 0 | if (!attempted_already) |
455 | 0 | { |
456 | 0 | auth_mech_to_use_gtype = find_mech_by_name (auth, supported_auth_mechs[n]); |
457 | 0 | if (auth_mech_to_use_gtype != (GType) 0) |
458 | 0 | break; |
459 | 0 | } |
460 | 0 | } |
461 | |
|
462 | 0 | if (auth_mech_to_use_gtype == (GType) 0) |
463 | 0 | { |
464 | 0 | guint n; |
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 | &initial_response_len); |
512 | | #if 0 |
513 | | g_printerr ("using auth mechanism with name '%s' of type '%s' with initial response '%s'\n", |
514 | | _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype), |
515 | | g_type_name (G_TYPE_FROM_INSTANCE (mech)), |
516 | | initial_response); |
517 | | #endif |
518 | 0 | if (initial_response != NULL) |
519 | 0 | { |
520 | | //g_printerr ("initial_response = '%s'\n", initial_response); |
521 | 0 | encoded = _g_dbus_hexencode (initial_response, initial_response_len); |
522 | 0 | s = g_strdup_printf ("AUTH %s %s\r\n", |
523 | 0 | _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype), |
524 | 0 | encoded); |
525 | 0 | g_free (initial_response); |
526 | 0 | g_free (encoded); |
527 | 0 | } |
528 | 0 | else |
529 | 0 | { |
530 | 0 | s = g_strdup_printf ("AUTH %s\r\n", _g_dbus_auth_mechanism_get_name (auth_mech_to_use_gtype)); |
531 | 0 | } |
532 | 0 | debug_print ("CLIENT: writing '%s'", s); |
533 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
534 | 0 | { |
535 | 0 | g_object_unref (mech); |
536 | 0 | mech = NULL; |
537 | 0 | g_free (s); |
538 | 0 | goto out; |
539 | 0 | } |
540 | 0 | g_free (s); |
541 | |
|
542 | 0 | out: |
543 | 0 | return mech; |
544 | 0 | } |
545 | | |
546 | | |
547 | | /* ---------------------------------------------------------------------------------------------------- */ |
548 | | |
549 | | typedef enum |
550 | | { |
551 | | CLIENT_STATE_WAITING_FOR_DATA, |
552 | | CLIENT_STATE_WAITING_FOR_OK, |
553 | | CLIENT_STATE_WAITING_FOR_REJECT, |
554 | | CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD |
555 | | } ClientState; |
556 | | |
557 | | gchar * |
558 | | _g_dbus_auth_run_client (GDBusAuth *auth, |
559 | | GDBusAuthObserver *observer, |
560 | | GDBusCapabilityFlags offered_capabilities, |
561 | | GDBusCapabilityFlags *out_negotiated_capabilities, |
562 | | GCancellable *cancellable, |
563 | | GError **error) |
564 | 0 | { |
565 | 0 | gchar *s; |
566 | 0 | GDataInputStream *dis; |
567 | 0 | GDataOutputStream *dos; |
568 | 0 | GCredentials *credentials; |
569 | 0 | gchar *ret_guid; |
570 | 0 | gchar *line; |
571 | 0 | gsize line_length; |
572 | 0 | gchar **supported_auth_mechs; |
573 | 0 | GPtrArray *attempted_auth_mechs; |
574 | 0 | GDBusAuthMechanism *mech; |
575 | 0 | ClientState state; |
576 | 0 | GDBusCapabilityFlags negotiated_capabilities; |
577 | |
|
578 | 0 | debug_print ("CLIENT: initiating"); |
579 | |
|
580 | 0 | _g_dbus_auth_add_mechs (auth, observer); |
581 | |
|
582 | 0 | ret_guid = NULL; |
583 | 0 | supported_auth_mechs = NULL; |
584 | 0 | attempted_auth_mechs = g_ptr_array_new (); |
585 | 0 | mech = NULL; |
586 | 0 | negotiated_capabilities = 0; |
587 | 0 | credentials = NULL; |
588 | |
|
589 | 0 | dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream))); |
590 | 0 | dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream))); |
591 | 0 | g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (dis), FALSE); |
592 | 0 | g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (dos), FALSE); |
593 | |
|
594 | 0 | g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); |
595 | |
|
596 | 0 | #ifdef G_OS_UNIX |
597 | 0 | if (G_IS_UNIX_CONNECTION (auth->priv->stream)) |
598 | 0 | { |
599 | 0 | credentials = g_credentials_new (); |
600 | 0 | if (!g_unix_connection_send_credentials (G_UNIX_CONNECTION (auth->priv->stream), |
601 | 0 | cancellable, |
602 | 0 | error)) |
603 | 0 | goto out; |
604 | 0 | } |
605 | 0 | else |
606 | 0 | { |
607 | 0 | if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error)) |
608 | 0 | goto out; |
609 | 0 | } |
610 | | #else |
611 | | if (!g_data_output_stream_put_byte (dos, '\0', cancellable, error)) |
612 | | goto out; |
613 | | #endif |
614 | | |
615 | 0 | if (credentials != NULL) |
616 | 0 | { |
617 | 0 | if (G_UNLIKELY (_g_dbus_debug_authentication ())) |
618 | 0 | { |
619 | 0 | s = g_credentials_to_string (credentials); |
620 | 0 | debug_print ("CLIENT: sent credentials '%s'", s); |
621 | 0 | g_free (s); |
622 | 0 | } |
623 | 0 | } |
624 | 0 | else |
625 | 0 | { |
626 | 0 | debug_print ("CLIENT: didn't send any credentials"); |
627 | 0 | } |
628 | | |
629 | | /* TODO: to reduce roundtrips, try to pick an auth mechanism to start with */ |
630 | | |
631 | | /* Get list of supported authentication mechanisms */ |
632 | 0 | s = "AUTH\r\n"; |
633 | 0 | debug_print ("CLIENT: writing '%s'", s); |
634 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
635 | 0 | goto out; |
636 | 0 | state = CLIENT_STATE_WAITING_FOR_REJECT; |
637 | |
|
638 | 0 | while (TRUE) |
639 | 0 | { |
640 | 0 | switch (state) |
641 | 0 | { |
642 | 0 | case CLIENT_STATE_WAITING_FOR_REJECT: |
643 | 0 | debug_print ("CLIENT: WaitingForReject"); |
644 | 0 | line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); |
645 | 0 | if (line == NULL) |
646 | 0 | goto out; |
647 | 0 | debug_print ("CLIENT: WaitingForReject, read '%s'", line); |
648 | |
|
649 | 0 | choose_mechanism: |
650 | 0 | if (!g_str_has_prefix (line, "REJECTED ")) |
651 | 0 | { |
652 | 0 | g_set_error (error, |
653 | 0 | G_IO_ERROR, |
654 | 0 | G_IO_ERROR_FAILED, |
655 | 0 | "In WaitingForReject: Expected 'REJECTED am1 am2 ... amN', got '%s'", |
656 | 0 | line); |
657 | 0 | g_free (line); |
658 | 0 | goto out; |
659 | 0 | } |
660 | 0 | if (supported_auth_mechs == NULL) |
661 | 0 | { |
662 | 0 | supported_auth_mechs = g_strsplit (line + sizeof ("REJECTED ") - 1, " ", 0); |
663 | | #if 0 |
664 | | for (n = 0; supported_auth_mechs != NULL && supported_auth_mechs[n] != NULL; n++) |
665 | | g_printerr ("supported_auth_mechs[%d] = '%s'\n", n, supported_auth_mechs[n]); |
666 | | #endif |
667 | 0 | } |
668 | 0 | g_free (line); |
669 | 0 | mech = client_choose_mech_and_send_initial_response (auth, |
670 | 0 | credentials, |
671 | 0 | (const gchar* const *) supported_auth_mechs, |
672 | 0 | attempted_auth_mechs, |
673 | 0 | dos, |
674 | 0 | cancellable, |
675 | 0 | error); |
676 | 0 | if (mech == NULL) |
677 | 0 | goto out; |
678 | 0 | if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA) |
679 | 0 | state = CLIENT_STATE_WAITING_FOR_DATA; |
680 | 0 | else |
681 | 0 | state = CLIENT_STATE_WAITING_FOR_OK; |
682 | 0 | break; |
683 | | |
684 | 0 | case CLIENT_STATE_WAITING_FOR_OK: |
685 | 0 | debug_print ("CLIENT: WaitingForOK"); |
686 | 0 | line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); |
687 | 0 | if (line == NULL) |
688 | 0 | goto out; |
689 | 0 | debug_print ("CLIENT: WaitingForOK, read '%s'", line); |
690 | 0 | if (g_str_has_prefix (line, "OK ")) |
691 | 0 | { |
692 | 0 | if (!g_dbus_is_guid (line + 3)) |
693 | 0 | { |
694 | 0 | g_set_error (error, |
695 | 0 | G_IO_ERROR, |
696 | 0 | G_IO_ERROR_FAILED, |
697 | 0 | "Invalid OK response '%s'", |
698 | 0 | line); |
699 | 0 | g_free (line); |
700 | 0 | goto out; |
701 | 0 | } |
702 | 0 | ret_guid = g_strdup (line + 3); |
703 | 0 | g_free (line); |
704 | |
|
705 | 0 | if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING) |
706 | 0 | { |
707 | 0 | s = "NEGOTIATE_UNIX_FD\r\n"; |
708 | 0 | debug_print ("CLIENT: writing '%s'", s); |
709 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
710 | 0 | goto out; |
711 | 0 | state = CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD; |
712 | 0 | } |
713 | 0 | else |
714 | 0 | { |
715 | 0 | s = "BEGIN\r\n"; |
716 | 0 | debug_print ("CLIENT: writing '%s'", s); |
717 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
718 | 0 | goto out; |
719 | | /* and we're done! */ |
720 | 0 | goto out; |
721 | 0 | } |
722 | 0 | } |
723 | 0 | else if (g_str_has_prefix (line, "REJECTED ")) |
724 | 0 | { |
725 | 0 | goto choose_mechanism; |
726 | 0 | } |
727 | 0 | else |
728 | 0 | { |
729 | | /* TODO: handle other valid responses */ |
730 | 0 | g_set_error (error, |
731 | 0 | G_IO_ERROR, |
732 | 0 | G_IO_ERROR_FAILED, |
733 | 0 | "In WaitingForOk: unexpected response '%s'", |
734 | 0 | line); |
735 | 0 | g_free (line); |
736 | 0 | goto out; |
737 | 0 | } |
738 | 0 | break; |
739 | | |
740 | 0 | case CLIENT_STATE_WAITING_FOR_AGREE_UNIX_FD: |
741 | 0 | debug_print ("CLIENT: WaitingForAgreeUnixFD"); |
742 | 0 | line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); |
743 | 0 | if (line == NULL) |
744 | 0 | goto out; |
745 | 0 | debug_print ("CLIENT: WaitingForAgreeUnixFD, read='%s'", line); |
746 | 0 | if (g_strcmp0 (line, "AGREE_UNIX_FD") == 0) |
747 | 0 | { |
748 | 0 | g_free (line); |
749 | 0 | negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING; |
750 | 0 | s = "BEGIN\r\n"; |
751 | 0 | debug_print ("CLIENT: writing '%s'", s); |
752 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
753 | 0 | goto out; |
754 | | /* and we're done! */ |
755 | 0 | goto out; |
756 | 0 | } |
757 | 0 | else if (g_str_has_prefix (line, "ERROR") && (line[5] == 0 || g_ascii_isspace (line[5]))) |
758 | 0 | { |
759 | | //g_strstrip (line + 5); g_debug ("bah, no unix_fd: '%s'", line + 5); |
760 | 0 | g_free (line); |
761 | 0 | s = "BEGIN\r\n"; |
762 | 0 | debug_print ("CLIENT: writing '%s'", s); |
763 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
764 | 0 | goto out; |
765 | | /* and we're done! */ |
766 | 0 | goto out; |
767 | 0 | } |
768 | 0 | else |
769 | 0 | { |
770 | | /* TODO: handle other valid responses */ |
771 | 0 | g_set_error (error, |
772 | 0 | G_IO_ERROR, |
773 | 0 | G_IO_ERROR_FAILED, |
774 | 0 | "In WaitingForAgreeUnixFd: unexpected response '%s'", |
775 | 0 | line); |
776 | 0 | g_free (line); |
777 | 0 | goto out; |
778 | 0 | } |
779 | 0 | break; |
780 | | |
781 | 0 | case CLIENT_STATE_WAITING_FOR_DATA: |
782 | 0 | debug_print ("CLIENT: WaitingForData"); |
783 | 0 | line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); |
784 | 0 | if (line == NULL) |
785 | 0 | goto out; |
786 | 0 | debug_print ("CLIENT: WaitingForData, read='%s'", line); |
787 | 0 | if (g_str_has_prefix (line, "DATA ")) |
788 | 0 | { |
789 | 0 | gchar *encoded; |
790 | 0 | gchar *decoded_data; |
791 | 0 | gsize decoded_data_len = 0; |
792 | |
|
793 | 0 | encoded = g_strdup (line + 5); |
794 | 0 | g_free (line); |
795 | 0 | g_strstrip (encoded); |
796 | 0 | decoded_data = hexdecode (encoded, &decoded_data_len, error); |
797 | 0 | g_free (encoded); |
798 | 0 | if (decoded_data == NULL) |
799 | 0 | { |
800 | 0 | g_prefix_error (error, "DATA response is malformed: "); |
801 | | /* invalid encoding, disconnect! */ |
802 | 0 | goto out; |
803 | 0 | } |
804 | 0 | _g_dbus_auth_mechanism_client_data_receive (mech, decoded_data, decoded_data_len); |
805 | 0 | g_free (decoded_data); |
806 | |
|
807 | 0 | if (_g_dbus_auth_mechanism_client_get_state (mech) == G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND) |
808 | 0 | { |
809 | 0 | gchar *data; |
810 | 0 | gsize data_len; |
811 | 0 | gchar *encoded_data; |
812 | 0 | data = _g_dbus_auth_mechanism_client_data_send (mech, &data_len); |
813 | 0 | encoded_data = _g_dbus_hexencode (data, data_len); |
814 | 0 | s = g_strdup_printf ("DATA %s\r\n", encoded_data); |
815 | 0 | g_free (encoded_data); |
816 | 0 | g_free (data); |
817 | 0 | debug_print ("CLIENT: writing '%s'", s); |
818 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
819 | 0 | { |
820 | 0 | g_free (s); |
821 | 0 | goto out; |
822 | 0 | } |
823 | 0 | g_free (s); |
824 | 0 | } |
825 | 0 | state = CLIENT_STATE_WAITING_FOR_OK; |
826 | 0 | } |
827 | 0 | else if (g_str_has_prefix (line, "REJECTED ")) |
828 | 0 | { |
829 | | /* could be the chosen authentication method just doesn't work. Try |
830 | | * another one... |
831 | | */ |
832 | 0 | goto choose_mechanism; |
833 | 0 | } |
834 | 0 | else |
835 | 0 | { |
836 | 0 | g_set_error (error, |
837 | 0 | G_IO_ERROR, |
838 | 0 | G_IO_ERROR_FAILED, |
839 | 0 | "In WaitingForData: unexpected response '%s'", |
840 | 0 | line); |
841 | 0 | g_free (line); |
842 | 0 | goto out; |
843 | 0 | } |
844 | 0 | break; |
845 | | |
846 | 0 | default: |
847 | 0 | g_assert_not_reached (); |
848 | 0 | break; |
849 | 0 | } |
850 | |
|
851 | 0 | }; /* main authentication client loop */ |
852 | |
|
853 | 0 | out: |
854 | 0 | if (mech != NULL) |
855 | 0 | g_object_unref (mech); |
856 | 0 | g_ptr_array_unref (attempted_auth_mechs); |
857 | 0 | g_strfreev (supported_auth_mechs); |
858 | 0 | g_object_unref (dis); |
859 | 0 | g_object_unref (dos); |
860 | | |
861 | | /* ensure return value is NULL if error is set */ |
862 | 0 | if (error != NULL && *error != NULL) |
863 | 0 | { |
864 | 0 | g_free (ret_guid); |
865 | 0 | ret_guid = NULL; |
866 | 0 | } |
867 | |
|
868 | 0 | if (ret_guid != NULL) |
869 | 0 | { |
870 | 0 | if (out_negotiated_capabilities != NULL) |
871 | 0 | *out_negotiated_capabilities = negotiated_capabilities; |
872 | 0 | } |
873 | |
|
874 | 0 | if (credentials != NULL) |
875 | 0 | g_object_unref (credentials); |
876 | |
|
877 | 0 | debug_print ("CLIENT: Done, authenticated=%d", ret_guid != NULL); |
878 | |
|
879 | 0 | return ret_guid; |
880 | 0 | } |
881 | | |
882 | | /* ---------------------------------------------------------------------------------------------------- */ |
883 | | |
884 | | static gchar * |
885 | | get_auth_mechanisms (GDBusAuth *auth, |
886 | | gboolean allow_anonymous, |
887 | | const gchar *prefix, |
888 | | const gchar *suffix, |
889 | | const gchar *separator) |
890 | 0 | { |
891 | 0 | GList *l; |
892 | 0 | GString *str; |
893 | 0 | gboolean need_sep; |
894 | |
|
895 | 0 | str = g_string_new (prefix); |
896 | 0 | need_sep = FALSE; |
897 | 0 | for (l = auth->priv->available_mechanisms; l != NULL; l = l->next) |
898 | 0 | { |
899 | 0 | Mechanism *m = l->data; |
900 | |
|
901 | 0 | if (!allow_anonymous && g_strcmp0 (m->name, "ANONYMOUS") == 0) |
902 | 0 | continue; |
903 | | |
904 | 0 | if (need_sep) |
905 | 0 | g_string_append (str, separator); |
906 | 0 | g_string_append (str, m->name); |
907 | 0 | need_sep = TRUE; |
908 | 0 | } |
909 | |
|
910 | 0 | g_string_append (str, suffix); |
911 | 0 | return g_string_free (str, FALSE); |
912 | 0 | } |
913 | | |
914 | | |
915 | | typedef enum |
916 | | { |
917 | | SERVER_STATE_WAITING_FOR_AUTH, |
918 | | SERVER_STATE_WAITING_FOR_DATA, |
919 | | SERVER_STATE_WAITING_FOR_BEGIN |
920 | | } ServerState; |
921 | | |
922 | | gboolean |
923 | | _g_dbus_auth_run_server (GDBusAuth *auth, |
924 | | GDBusAuthObserver *observer, |
925 | | const gchar *guid, |
926 | | gboolean allow_anonymous, |
927 | | gboolean require_same_user, |
928 | | GDBusCapabilityFlags offered_capabilities, |
929 | | GDBusCapabilityFlags *out_negotiated_capabilities, |
930 | | GCredentials **out_received_credentials, |
931 | | GCancellable *cancellable, |
932 | | GError **error) |
933 | 0 | { |
934 | 0 | gboolean ret; |
935 | 0 | ServerState state; |
936 | 0 | GDataInputStream *dis; |
937 | 0 | GDataOutputStream *dos; |
938 | 0 | GError *local_error; |
939 | 0 | gchar *line; |
940 | 0 | gsize line_length; |
941 | 0 | GDBusAuthMechanism *mech; |
942 | 0 | gchar *s; |
943 | 0 | GDBusCapabilityFlags negotiated_capabilities; |
944 | 0 | GCredentials *credentials; |
945 | 0 | GCredentials *own_credentials = NULL; |
946 | |
|
947 | 0 | debug_print ("SERVER: initiating"); |
948 | |
|
949 | 0 | _g_dbus_auth_add_mechs (auth, observer); |
950 | |
|
951 | 0 | ret = FALSE; |
952 | 0 | dis = NULL; |
953 | 0 | dos = NULL; |
954 | 0 | mech = NULL; |
955 | 0 | negotiated_capabilities = 0; |
956 | 0 | credentials = NULL; |
957 | |
|
958 | 0 | if (!g_dbus_is_guid (guid)) |
959 | 0 | { |
960 | 0 | g_set_error (error, |
961 | 0 | G_IO_ERROR, |
962 | 0 | G_IO_ERROR_FAILED, |
963 | 0 | "The given guid '%s' is not valid", |
964 | 0 | guid); |
965 | 0 | goto out; |
966 | 0 | } |
967 | | |
968 | 0 | dis = G_DATA_INPUT_STREAM (g_data_input_stream_new (g_io_stream_get_input_stream (auth->priv->stream))); |
969 | 0 | dos = G_DATA_OUTPUT_STREAM (g_data_output_stream_new (g_io_stream_get_output_stream (auth->priv->stream))); |
970 | 0 | g_filter_input_stream_set_close_base_stream (G_FILTER_INPUT_STREAM (dis), FALSE); |
971 | 0 | g_filter_output_stream_set_close_base_stream (G_FILTER_OUTPUT_STREAM (dos), FALSE); |
972 | |
|
973 | 0 | g_data_input_stream_set_newline_type (dis, G_DATA_STREAM_NEWLINE_TYPE_CR_LF); |
974 | | |
975 | | /* read the NUL-byte, possibly with credentials attached */ |
976 | 0 | #ifdef G_OS_UNIX |
977 | 0 | #ifndef G_CREDENTIALS_PREFER_MESSAGE_PASSING |
978 | 0 | if (G_IS_SOCKET_CONNECTION (auth->priv->stream)) |
979 | 0 | { |
980 | 0 | GSocket *sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (auth->priv->stream)); |
981 | |
|
982 | 0 | local_error = NULL; |
983 | 0 | credentials = g_socket_get_credentials (sock, &local_error); |
984 | |
|
985 | 0 | if (credentials == NULL && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) |
986 | 0 | { |
987 | 0 | g_propagate_error (error, local_error); |
988 | 0 | goto out; |
989 | 0 | } |
990 | 0 | else |
991 | 0 | { |
992 | | /* Clear the error indicator, so we can retry with |
993 | | * g_unix_connection_receive_credentials() if necessary */ |
994 | 0 | g_clear_error (&local_error); |
995 | 0 | } |
996 | 0 | } |
997 | 0 | #endif |
998 | | |
999 | 0 | if (credentials == NULL && G_IS_UNIX_CONNECTION (auth->priv->stream)) |
1000 | 0 | { |
1001 | 0 | local_error = NULL; |
1002 | 0 | credentials = g_unix_connection_receive_credentials (G_UNIX_CONNECTION (auth->priv->stream), |
1003 | 0 | cancellable, |
1004 | 0 | &local_error); |
1005 | 0 | if (credentials == NULL && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) |
1006 | 0 | { |
1007 | 0 | g_propagate_error (error, local_error); |
1008 | 0 | goto out; |
1009 | 0 | } |
1010 | 0 | } |
1011 | 0 | else |
1012 | 0 | { |
1013 | 0 | local_error = NULL; |
1014 | 0 | (void)g_data_input_stream_read_byte (dis, cancellable, &local_error); |
1015 | 0 | if (local_error != NULL) |
1016 | 0 | { |
1017 | 0 | g_propagate_error (error, local_error); |
1018 | 0 | goto out; |
1019 | 0 | } |
1020 | 0 | } |
1021 | | #else |
1022 | | local_error = NULL; |
1023 | | (void)g_data_input_stream_read_byte (dis, cancellable, &local_error); |
1024 | | if (local_error != NULL) |
1025 | | { |
1026 | | g_propagate_error (error, local_error); |
1027 | | goto out; |
1028 | | } |
1029 | | #endif |
1030 | 0 | if (credentials != NULL) |
1031 | 0 | { |
1032 | 0 | if (G_UNLIKELY (_g_dbus_debug_authentication ())) |
1033 | 0 | { |
1034 | 0 | s = g_credentials_to_string (credentials); |
1035 | 0 | debug_print ("SERVER: received credentials '%s'", s); |
1036 | 0 | g_free (s); |
1037 | 0 | } |
1038 | 0 | } |
1039 | 0 | else |
1040 | 0 | { |
1041 | 0 | debug_print ("SERVER: didn't receive any credentials"); |
1042 | 0 | } |
1043 | |
|
1044 | 0 | own_credentials = g_credentials_new (); |
1045 | |
|
1046 | 0 | state = SERVER_STATE_WAITING_FOR_AUTH; |
1047 | 0 | while (TRUE) |
1048 | 0 | { |
1049 | 0 | switch (state) |
1050 | 0 | { |
1051 | 0 | case SERVER_STATE_WAITING_FOR_AUTH: |
1052 | 0 | debug_print ("SERVER: WaitingForAuth"); |
1053 | 0 | line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); |
1054 | 0 | debug_print ("SERVER: WaitingForAuth, read '%s'", line); |
1055 | 0 | if (line == NULL) |
1056 | 0 | goto out; |
1057 | 0 | if (g_strcmp0 (line, "AUTH") == 0) |
1058 | 0 | { |
1059 | 0 | s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " "); |
1060 | 0 | debug_print ("SERVER: writing '%s'", s); |
1061 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1062 | 0 | { |
1063 | 0 | g_free (s); |
1064 | 0 | g_free (line); |
1065 | 0 | goto out; |
1066 | 0 | } |
1067 | 0 | g_free (s); |
1068 | 0 | g_free (line); |
1069 | 0 | } |
1070 | 0 | else if (g_str_has_prefix (line, "AUTH ")) |
1071 | 0 | { |
1072 | 0 | gchar **tokens; |
1073 | 0 | const gchar *encoded; |
1074 | 0 | const gchar *mech_name; |
1075 | 0 | GType auth_mech_to_use_gtype; |
1076 | |
|
1077 | 0 | tokens = g_strsplit (line, " ", 0); |
1078 | |
|
1079 | 0 | switch (g_strv_length (tokens)) |
1080 | 0 | { |
1081 | 0 | case 2: |
1082 | | /* no initial response */ |
1083 | 0 | mech_name = tokens[1]; |
1084 | 0 | encoded = NULL; |
1085 | 0 | break; |
1086 | | |
1087 | 0 | case 3: |
1088 | | /* initial response */ |
1089 | 0 | mech_name = tokens[1]; |
1090 | 0 | encoded = tokens[2]; |
1091 | 0 | break; |
1092 | | |
1093 | 0 | default: |
1094 | 0 | g_set_error (error, |
1095 | 0 | G_IO_ERROR, |
1096 | 0 | G_IO_ERROR_FAILED, |
1097 | 0 | "Unexpected line '%s' while in WaitingForAuth state", |
1098 | 0 | line); |
1099 | 0 | g_strfreev (tokens); |
1100 | 0 | g_free (line); |
1101 | 0 | goto out; |
1102 | 0 | } |
1103 | | |
1104 | 0 | g_free (line); |
1105 | | |
1106 | | /* TODO: record that the client has attempted to use this mechanism */ |
1107 | | //g_debug ("client is trying '%s'", mech_name); |
1108 | |
|
1109 | 0 | auth_mech_to_use_gtype = find_mech_by_name (auth, mech_name); |
1110 | 0 | if ((auth_mech_to_use_gtype == (GType) 0) || |
1111 | 0 | (!allow_anonymous && g_strcmp0 (mech_name, "ANONYMOUS") == 0)) |
1112 | 0 | { |
1113 | | /* We don't support this auth mechanism */ |
1114 | 0 | g_strfreev (tokens); |
1115 | 0 | s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " "); |
1116 | 0 | debug_print ("SERVER: writing '%s'", s); |
1117 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1118 | 0 | { |
1119 | 0 | g_free (s); |
1120 | 0 | goto out; |
1121 | 0 | } |
1122 | 0 | g_free (s); |
1123 | | |
1124 | | /* stay in WAITING FOR AUTH */ |
1125 | 0 | state = SERVER_STATE_WAITING_FOR_AUTH; |
1126 | 0 | } |
1127 | 0 | else |
1128 | 0 | { |
1129 | 0 | gchar *initial_response; |
1130 | 0 | gsize initial_response_len; |
1131 | |
|
1132 | 0 | g_clear_object (&mech); |
1133 | 0 | mech = g_object_new (auth_mech_to_use_gtype, |
1134 | 0 | "stream", auth->priv->stream, |
1135 | 0 | "credentials", credentials, |
1136 | 0 | NULL); |
1137 | |
|
1138 | 0 | initial_response = NULL; |
1139 | 0 | initial_response_len = 0; |
1140 | 0 | if (encoded != NULL) |
1141 | 0 | { |
1142 | 0 | initial_response = hexdecode (encoded, &initial_response_len, error); |
1143 | 0 | if (initial_response == NULL) |
1144 | 0 | { |
1145 | 0 | g_prefix_error (error, "Initial response is malformed: "); |
1146 | | /* invalid encoding, disconnect! */ |
1147 | 0 | g_strfreev (tokens); |
1148 | 0 | goto out; |
1149 | 0 | } |
1150 | 0 | } |
1151 | | |
1152 | 0 | _g_dbus_auth_mechanism_server_initiate (mech, |
1153 | 0 | initial_response, |
1154 | 0 | initial_response_len); |
1155 | 0 | g_free (initial_response); |
1156 | 0 | g_strfreev (tokens); |
1157 | |
|
1158 | 0 | change_state: |
1159 | 0 | switch (_g_dbus_auth_mechanism_server_get_state (mech)) |
1160 | 0 | { |
1161 | 0 | case G_DBUS_AUTH_MECHANISM_STATE_ACCEPTED: |
1162 | 0 | if (require_same_user && |
1163 | 0 | (credentials == NULL || |
1164 | 0 | !g_credentials_is_same_user (credentials, own_credentials, NULL))) |
1165 | 0 | { |
1166 | | /* disconnect */ |
1167 | 0 | g_set_error_literal (error, |
1168 | 0 | G_IO_ERROR, |
1169 | 0 | G_IO_ERROR_FAILED, |
1170 | 0 | _("User IDs must be the same for peer and server")); |
1171 | 0 | goto out; |
1172 | 0 | } |
1173 | 0 | else if (observer != NULL && |
1174 | 0 | !g_dbus_auth_observer_authorize_authenticated_peer (observer, |
1175 | 0 | auth->priv->stream, |
1176 | 0 | credentials)) |
1177 | 0 | { |
1178 | | /* disconnect */ |
1179 | 0 | g_set_error_literal (error, |
1180 | 0 | G_IO_ERROR, |
1181 | 0 | G_IO_ERROR_FAILED, |
1182 | 0 | _("Cancelled via GDBusAuthObserver::authorize-authenticated-peer")); |
1183 | 0 | goto out; |
1184 | 0 | } |
1185 | 0 | else |
1186 | 0 | { |
1187 | 0 | s = g_strdup_printf ("OK %s\r\n", guid); |
1188 | 0 | debug_print ("SERVER: writing '%s'", s); |
1189 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1190 | 0 | { |
1191 | 0 | g_free (s); |
1192 | 0 | goto out; |
1193 | 0 | } |
1194 | 0 | g_free (s); |
1195 | 0 | state = SERVER_STATE_WAITING_FOR_BEGIN; |
1196 | 0 | } |
1197 | 0 | break; |
1198 | | |
1199 | 0 | case G_DBUS_AUTH_MECHANISM_STATE_REJECTED: |
1200 | 0 | s = get_auth_mechanisms (auth, allow_anonymous, "REJECTED ", "\r\n", " "); |
1201 | 0 | debug_print ("SERVER: writing '%s'", s); |
1202 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1203 | 0 | { |
1204 | 0 | g_free (s); |
1205 | 0 | goto out; |
1206 | 0 | } |
1207 | 0 | g_free (s); |
1208 | 0 | state = SERVER_STATE_WAITING_FOR_AUTH; |
1209 | 0 | break; |
1210 | | |
1211 | 0 | case G_DBUS_AUTH_MECHANISM_STATE_WAITING_FOR_DATA: |
1212 | 0 | state = SERVER_STATE_WAITING_FOR_DATA; |
1213 | 0 | break; |
1214 | | |
1215 | 0 | case G_DBUS_AUTH_MECHANISM_STATE_HAVE_DATA_TO_SEND: |
1216 | 0 | { |
1217 | 0 | gchar *data; |
1218 | 0 | gsize data_len; |
1219 | |
|
1220 | 0 | data = _g_dbus_auth_mechanism_server_data_send (mech, &data_len); |
1221 | 0 | if (data != NULL) |
1222 | 0 | { |
1223 | 0 | gchar *encoded_data; |
1224 | |
|
1225 | 0 | encoded_data = _g_dbus_hexencode (data, data_len); |
1226 | 0 | s = g_strdup_printf ("DATA %s\r\n", encoded_data); |
1227 | 0 | g_free (encoded_data); |
1228 | 0 | g_free (data); |
1229 | |
|
1230 | 0 | debug_print ("SERVER: writing '%s'", s); |
1231 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1232 | 0 | { |
1233 | 0 | g_free (s); |
1234 | 0 | goto out; |
1235 | 0 | } |
1236 | 0 | g_free (s); |
1237 | 0 | } |
1238 | 0 | } |
1239 | 0 | goto change_state; |
1240 | 0 | break; |
1241 | | |
1242 | 0 | default: |
1243 | | /* TODO */ |
1244 | 0 | g_assert_not_reached (); |
1245 | 0 | break; |
1246 | 0 | } |
1247 | 0 | } |
1248 | 0 | } |
1249 | 0 | else |
1250 | 0 | { |
1251 | 0 | g_set_error (error, |
1252 | 0 | G_IO_ERROR, |
1253 | 0 | G_IO_ERROR_FAILED, |
1254 | 0 | "Unexpected line '%s' while in WaitingForAuth state", |
1255 | 0 | line); |
1256 | 0 | g_free (line); |
1257 | 0 | goto out; |
1258 | 0 | } |
1259 | 0 | break; |
1260 | | |
1261 | 0 | case SERVER_STATE_WAITING_FOR_DATA: |
1262 | 0 | debug_print ("SERVER: WaitingForData"); |
1263 | 0 | line = _my_g_data_input_stream_read_line (dis, &line_length, cancellable, error); |
1264 | 0 | debug_print ("SERVER: WaitingForData, read '%s'", line); |
1265 | 0 | if (line == NULL) |
1266 | 0 | goto out; |
1267 | 0 | if (g_str_has_prefix (line, "DATA ")) |
1268 | 0 | { |
1269 | 0 | gchar *encoded; |
1270 | 0 | gchar *decoded_data; |
1271 | 0 | gsize decoded_data_len = 0; |
1272 | |
|
1273 | 0 | encoded = g_strdup (line + 5); |
1274 | 0 | g_free (line); |
1275 | 0 | g_strstrip (encoded); |
1276 | 0 | decoded_data = hexdecode (encoded, &decoded_data_len, error); |
1277 | 0 | g_free (encoded); |
1278 | 0 | if (decoded_data == NULL) |
1279 | 0 | { |
1280 | 0 | g_prefix_error (error, "DATA response is malformed: "); |
1281 | | /* invalid encoding, disconnect! */ |
1282 | 0 | goto out; |
1283 | 0 | } |
1284 | 0 | _g_dbus_auth_mechanism_server_data_receive (mech, decoded_data, decoded_data_len); |
1285 | 0 | g_free (decoded_data); |
1286 | | /* oh man, this goto-crap is so ugly.. really need to rewrite the state machine */ |
1287 | 0 | goto change_state; |
1288 | 0 | } |
1289 | 0 | else |
1290 | 0 | { |
1291 | 0 | g_set_error (error, |
1292 | 0 | G_IO_ERROR, |
1293 | 0 | G_IO_ERROR_FAILED, |
1294 | 0 | "Unexpected line '%s' while in WaitingForData state", |
1295 | 0 | line); |
1296 | 0 | g_free (line); |
1297 | 0 | } |
1298 | 0 | goto out; |
1299 | | |
1300 | 0 | case SERVER_STATE_WAITING_FOR_BEGIN: |
1301 | 0 | debug_print ("SERVER: WaitingForBegin"); |
1302 | | /* Use extremely slow (but reliable) line reader - this basically |
1303 | | * does a recvfrom() system call per character |
1304 | | * |
1305 | | * (the problem with using GDataInputStream's read_line is that because of |
1306 | | * buffering it might start reading into the first D-Bus message that |
1307 | | * appears after "BEGIN\r\n"....) |
1308 | | */ |
1309 | 0 | line = _my_g_input_stream_read_line_safe (g_io_stream_get_input_stream (auth->priv->stream), |
1310 | 0 | &line_length, |
1311 | 0 | cancellable, |
1312 | 0 | error); |
1313 | 0 | if (line == NULL) |
1314 | 0 | goto out; |
1315 | 0 | debug_print ("SERVER: WaitingForBegin, read '%s'", line); |
1316 | 0 | if (g_strcmp0 (line, "BEGIN") == 0) |
1317 | 0 | { |
1318 | | /* YAY, done! */ |
1319 | 0 | ret = TRUE; |
1320 | 0 | g_free (line); |
1321 | 0 | goto out; |
1322 | 0 | } |
1323 | 0 | else if (g_strcmp0 (line, "NEGOTIATE_UNIX_FD") == 0) |
1324 | 0 | { |
1325 | 0 | g_free (line); |
1326 | 0 | if (offered_capabilities & G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING) |
1327 | 0 | { |
1328 | 0 | negotiated_capabilities |= G_DBUS_CAPABILITY_FLAGS_UNIX_FD_PASSING; |
1329 | 0 | s = "AGREE_UNIX_FD\r\n"; |
1330 | 0 | debug_print ("SERVER: writing '%s'", s); |
1331 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1332 | 0 | goto out; |
1333 | 0 | } |
1334 | 0 | else |
1335 | 0 | { |
1336 | 0 | s = "ERROR \"fd passing not offered\"\r\n"; |
1337 | 0 | debug_print ("SERVER: writing '%s'", s); |
1338 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1339 | 0 | goto out; |
1340 | 0 | } |
1341 | 0 | } |
1342 | 0 | else |
1343 | 0 | { |
1344 | 0 | g_debug ("Unexpected line '%s' while in WaitingForBegin state", line); |
1345 | 0 | g_free (line); |
1346 | 0 | s = "ERROR \"Unknown Command\"\r\n"; |
1347 | 0 | debug_print ("SERVER: writing '%s'", s); |
1348 | 0 | if (!g_data_output_stream_put_string (dos, s, cancellable, error)) |
1349 | 0 | goto out; |
1350 | 0 | } |
1351 | 0 | break; |
1352 | | |
1353 | 0 | default: |
1354 | 0 | g_assert_not_reached (); |
1355 | 0 | break; |
1356 | 0 | } |
1357 | 0 | } |
1358 | | |
1359 | | |
1360 | 0 | g_set_error_literal (error, |
1361 | 0 | G_IO_ERROR, |
1362 | 0 | G_IO_ERROR_FAILED, |
1363 | 0 | "Not implemented (server)"); |
1364 | |
|
1365 | 0 | out: |
1366 | 0 | g_clear_object (&mech); |
1367 | 0 | g_clear_object (&dis); |
1368 | 0 | g_clear_object (&dos); |
1369 | 0 | g_clear_object (&own_credentials); |
1370 | | |
1371 | | /* ensure return value is FALSE if error is set */ |
1372 | 0 | if (error != NULL && *error != NULL) |
1373 | 0 | { |
1374 | 0 | ret = FALSE; |
1375 | 0 | } |
1376 | |
|
1377 | 0 | if (ret) |
1378 | 0 | { |
1379 | 0 | if (out_negotiated_capabilities != NULL) |
1380 | 0 | *out_negotiated_capabilities = negotiated_capabilities; |
1381 | 0 | if (out_received_credentials != NULL) |
1382 | 0 | *out_received_credentials = credentials != NULL ? g_object_ref (credentials) : NULL; |
1383 | 0 | } |
1384 | |
|
1385 | 0 | if (credentials != NULL) |
1386 | 0 | g_object_unref (credentials); |
1387 | |
|
1388 | 0 | debug_print ("SERVER: Done, authenticated=%d", ret); |
1389 | |
|
1390 | 0 | return ret; |
1391 | 0 | } |
1392 | | |
1393 | | /* ---------------------------------------------------------------------------------------------------- */ |