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