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