1 /*
2 * Copyright 2019 Collabora Ltd.
3 *
4 * SPDX-License-Identifier: LGPL-2.1-or-later
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General
17 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21
22 #include <errno.h>
23
24 #include <glib/gstdio.h>
25 #include <gio/gio.h>
26
27 /* For G_CREDENTIALS_*_SUPPORTED */
28 #include <gio/gcredentialsprivate.h>
29
30 #ifdef HAVE_DBUS1
31 #include <dbus/dbus.h>
32 #endif
33
34 typedef enum
35 {
36 INTEROP_FLAGS_EXTERNAL = (1 << 0),
37 INTEROP_FLAGS_ANONYMOUS = (1 << 1),
38 INTEROP_FLAGS_SHA1 = (1 << 2),
39 INTEROP_FLAGS_TCP = (1 << 3),
40 INTEROP_FLAGS_LIBDBUS = (1 << 4),
41 INTEROP_FLAGS_ABSTRACT = (1 << 5),
42 INTEROP_FLAGS_REQUIRE_SAME_USER = (1 << 6),
43 INTEROP_FLAGS_NONE = 0
44 } InteropFlags;
45
46 static gboolean
47 allow_external_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
48 const char *mechanism,
49 G_GNUC_UNUSED gpointer user_data)
50 {
51 if (g_strcmp0 (mechanism, "EXTERNAL") == 0)
52 {
53 g_debug ("Accepting EXTERNAL authentication");
54 return TRUE;
55 }
56 else
57 {
58 g_debug ("Rejecting \"%s\" authentication: not EXTERNAL", mechanism);
59 return FALSE;
60 }
61 }
62
63 static gboolean
64 allow_anonymous_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
65 const char *mechanism,
66 G_GNUC_UNUSED gpointer user_data)
67 {
68 if (g_strcmp0 (mechanism, "ANONYMOUS") == 0)
69 {
70 g_debug ("Accepting ANONYMOUS authentication");
71 return TRUE;
72 }
73 else
74 {
75 g_debug ("Rejecting \"%s\" authentication: not ANONYMOUS", mechanism);
76 return FALSE;
77 }
78 }
79
80 static gboolean
81 allow_sha1_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
82 const char *mechanism,
83 G_GNUC_UNUSED gpointer user_data)
84 {
85 if (g_strcmp0 (mechanism, "DBUS_COOKIE_SHA1") == 0)
86 {
87 g_debug ("Accepting DBUS_COOKIE_SHA1 authentication");
88 return TRUE;
89 }
90 else
91 {
92 g_debug ("Rejecting \"%s\" authentication: not DBUS_COOKIE_SHA1",
93 mechanism);
94 return FALSE;
95 }
96 }
97
98 static gboolean
99 allow_any_mechanism_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
100 const char *mechanism,
101 G_GNUC_UNUSED gpointer user_data)
102 {
103 g_debug ("Accepting \"%s\" authentication", mechanism);
104 return TRUE;
105 }
106
107 static gboolean
108 authorize_any_authenticated_peer_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
109 G_GNUC_UNUSED GIOStream *stream,
110 GCredentials *credentials,
111 G_GNUC_UNUSED gpointer user_data)
112 {
113 if (credentials == NULL)
114 {
115 g_debug ("Authorizing peer with no credentials");
116 }
117 else
118 {
119 gchar *str = g_credentials_to_string (credentials);
120
121 g_debug ("Authorizing peer with credentials: %s", str);
122 g_free (str);
123 }
124
125 return TRUE;
126 }
127
128 static GDBusMessage *
129 whoami_filter_cb (GDBusConnection *connection,
130 GDBusMessage *message,
131 gboolean incoming,
132 G_GNUC_UNUSED gpointer user_data)
133 {
134 if (!incoming)
135 return message;
136
137 if (g_dbus_message_get_message_type (message) == G_DBUS_MESSAGE_TYPE_METHOD_CALL &&
138 g_strcmp0 (g_dbus_message_get_member (message), "WhoAmI") == 0)
139 {
140 GDBusMessage *reply = g_dbus_message_new_method_reply (message);
141 gint64 uid = -1;
142 gint64 pid = -1;
143 #ifdef G_OS_UNIX
144 GCredentials *credentials = g_dbus_connection_get_peer_credentials (connection);
145
146 if (credentials != NULL)
147 {
148 uid = (gint64) g_credentials_get_unix_user (credentials, NULL);
149 pid = (gint64) g_credentials_get_unix_pid (credentials, NULL);
150 }
151 #endif
152
153 g_dbus_message_set_body (reply,
154 g_variant_new ("(xx)", uid, pid));
155 g_dbus_connection_send_message (connection, reply,
156 G_DBUS_SEND_MESSAGE_FLAGS_NONE,
157 NULL, NULL);
158 g_object_unref (reply);
159
160 /* handled */
161 g_object_unref (message);
162 return NULL;
163 }
164
165 return message;
166 }
167
168 static gboolean
169 new_connection_cb (G_GNUC_UNUSED GDBusServer *server,
170 GDBusConnection *connection,
171 G_GNUC_UNUSED gpointer user_data)
172 {
173 GCredentials *credentials = g_dbus_connection_get_peer_credentials (connection);
174
175 if (credentials == NULL)
176 {
177 g_debug ("New connection from peer with no credentials");
178 }
179 else
180 {
181 gchar *str = g_credentials_to_string (credentials);
182
183 g_debug ("New connection from peer with credentials: %s", str);
184 g_free (str);
185 }
186
187 g_object_ref (connection);
188 g_dbus_connection_add_filter (connection, whoami_filter_cb, NULL, NULL);
189 return TRUE;
190 }
191
192 #ifdef HAVE_DBUS1
193 typedef struct
194 {
195 DBusError error;
196 DBusConnection *conn;
197 DBusMessage *call;
198 DBusMessage *reply;
199 } LibdbusCall;
200
201 static void
202 libdbus_call_task_cb (GTask *task,
203 G_GNUC_UNUSED gpointer source_object,
204 gpointer task_data,
205 G_GNUC_UNUSED GCancellable *cancellable)
206 {
207 LibdbusCall *libdbus_call = task_data;
208
209 libdbus_call->reply = dbus_connection_send_with_reply_and_block (libdbus_call->conn,
210 libdbus_call->call,
211 -1,
212 &libdbus_call->error);
213 g_task_return_boolean (task, TRUE);
214 }
215 #endif /* HAVE_DBUS1 */
216
217 static void
218 store_result_cb (G_GNUC_UNUSED GObject *source_object,
219 GAsyncResult *res,
220 gpointer user_data)
221 {
222 GAsyncResult **result = user_data;
223
224 g_assert_nonnull (result);
225 g_assert_null (*result);
226 *result = g_object_ref (res);
227 }
228
229 static void
230 assert_expected_uid_pid (InteropFlags flags,
231 gint64 uid,
232 gint64 pid)
233 {
234 #ifdef G_OS_UNIX
235 if (flags & (INTEROP_FLAGS_ANONYMOUS | INTEROP_FLAGS_SHA1 | INTEROP_FLAGS_TCP))
236 {
237 /* No assertion. There is no guarantee whether credentials will be
238 * passed even though we didn't send them. Conversely, if
239 * credentials were not passed,
240 * g_dbus_connection_get_peer_credentials() always returns the
241 * credentials of the socket, and not the uid that a
242 * client might have proved it has by using DBUS_COOKIE_SHA1. */
243 }
244 else /* We should prefer EXTERNAL whenever it is allowed. */
245 {
246 #ifdef __linux__
247 /* We know that both GDBus and libdbus support full credentials-passing
248 * on Linux. */
249 g_assert_cmpint (uid, ==, getuid ());
250 g_assert_cmpint (pid, ==, getpid ());
251 #elif defined(__APPLE__)
252 /* We know (or at least suspect) that both GDBus and libdbus support
253 * passing the uid only on macOS. */
254 g_assert_cmpint (uid, ==, getuid ());
255 /* No pid here */
256 #else
257 g_test_message ("Please open a merge request to add appropriate "
258 "assertions for your platform");
259 #endif
260 }
261 #endif /* G_OS_UNIX */
262 }
263
264 static void
265 do_test_server_auth (InteropFlags flags)
266 {
267 GError *error = NULL;
268 gchar *tmpdir = NULL;
269 gchar *listenable_address = NULL;
270 GDBusServer *server = NULL;
271 GDBusAuthObserver *observer = NULL;
272 GDBusServerFlags server_flags = G_DBUS_SERVER_FLAGS_RUN_IN_THREAD;
273 gchar *guid = NULL;
274 const char *connectable_address;
275 GDBusConnection *client = NULL;
276 GAsyncResult *result = NULL;
277 GVariant *tuple = NULL;
278 gint64 uid, pid;
279 #ifdef HAVE_DBUS1
280 /* GNOME/glib#1831 seems to involve a race condition, so try a few times
281 * to see if we can trigger it. */
282 gsize i;
283 gsize n = 20;
284 #endif
285
286 if (flags & INTEROP_FLAGS_TCP)
287 {
288 listenable_address = g_strdup ("tcp:host=127.0.0.1");
289 }
290 else
291 {
292 #ifdef G_OS_UNIX
293 gchar *escaped;
294
295 tmpdir = g_dir_make_tmp ("gdbus-server-auth-XXXXXX", &error);
296 g_assert_no_error (error);
297 escaped = g_dbus_address_escape_value (tmpdir);
298 listenable_address = g_strdup_printf ("unix:%s=%s",
299 (flags & INTEROP_FLAGS_ABSTRACT) ? "tmpdir" : "dir",
300 escaped);
301 g_free (escaped);
302 #else
303 g_test_skip ("unix: addresses only work on Unix");
304 goto out;
305 #endif
306 }
307
308 g_test_message ("Testing GDBus server at %s / libdbus client, with flags: "
309 "external:%s "
310 "anonymous:%s "
311 "sha1:%s "
312 "abstract:%s "
313 "tcp:%s",
314 listenable_address,
315 (flags & INTEROP_FLAGS_EXTERNAL) ? "true" : "false",
316 (flags & INTEROP_FLAGS_ANONYMOUS) ? "true" : "false",
317 (flags & INTEROP_FLAGS_SHA1) ? "true" : "false",
318 (flags & INTEROP_FLAGS_ABSTRACT) ? "true" : "false",
319 (flags & INTEROP_FLAGS_TCP) ? "true" : "false");
320
321 #if !defined(G_CREDENTIALS_UNIX_CREDENTIALS_MESSAGE_SUPPORTED) \
322 && !defined(G_CREDENTIALS_SOCKET_GET_CREDENTIALS_SUPPORTED)
323 if (flags & INTEROP_FLAGS_EXTERNAL)
324 {
325 g_test_skip ("EXTERNAL authentication not implemented on this platform");
326 goto out;
327 }
328 #endif
329
330 if (flags & INTEROP_FLAGS_ANONYMOUS)
331 server_flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
332 if (flags & INTEROP_FLAGS_REQUIRE_SAME_USER)
333 server_flags |= G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER;
334
335 observer = g_dbus_auth_observer_new ();
336
337 if (flags & INTEROP_FLAGS_EXTERNAL)
338 g_signal_connect (observer, "allow-mechanism",
339 G_CALLBACK (allow_external_cb), NULL);
340 else if (flags & INTEROP_FLAGS_ANONYMOUS)
341 g_signal_connect (observer, "allow-mechanism",
342 G_CALLBACK (allow_anonymous_cb), NULL);
343 else if (flags & INTEROP_FLAGS_SHA1)
344 g_signal_connect (observer, "allow-mechanism",
345 G_CALLBACK (allow_sha1_cb), NULL);
346 else
347 g_signal_connect (observer, "allow-mechanism",
348 G_CALLBACK (allow_any_mechanism_cb), NULL);
349
350 g_signal_connect (observer, "authorize-authenticated-peer",
351 G_CALLBACK (authorize_any_authenticated_peer_cb),
352 NULL);
353
354 guid = g_dbus_generate_guid ();
355 server = g_dbus_server_new_sync (listenable_address,
356 server_flags,
357 guid,
358 observer,
359 NULL,
360 &error);
361 g_assert_no_error (error);
362 g_assert_nonnull (server);
363 g_signal_connect (server, "new-connection", G_CALLBACK (new_connection_cb), NULL);
364 g_dbus_server_start (server);
365 connectable_address = g_dbus_server_get_client_address (server);
366 g_test_message ("Connectable address: %s", connectable_address);
367
368 result = NULL;
369 g_dbus_connection_new_for_address (connectable_address,
370 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
371 NULL, NULL, store_result_cb, &result);
372
373 while (result == NULL)
374 g_main_context_iteration (NULL, TRUE);
375
376 client = g_dbus_connection_new_for_address_finish (result, &error);
377 g_assert_no_error (error);
378 g_assert_nonnull (client);
379 g_clear_object (&result);
380
381 g_dbus_connection_call (client, NULL, "/", "com.example.Test", "WhoAmI",
382 NULL, G_VARIANT_TYPE ("(xx)"),
383 G_DBUS_CALL_FLAGS_NONE, -1, NULL, store_result_cb,
384 &result);
385
386 while (result == NULL)
387 g_main_context_iteration (NULL, TRUE);
388
389 tuple = g_dbus_connection_call_finish (client, result, &error);
390 g_assert_no_error (error);
391 g_assert_nonnull (tuple);
392 g_clear_object (&result);
393 g_clear_object (&client);
394
395 uid = -2;
396 pid = -2;
397 g_variant_get (tuple, "(xx)", &uid, &pid);
398
399 g_debug ("Server says GDBus client is uid %" G_GINT64_FORMAT ", pid %" G_GINT64_FORMAT,
400 uid, pid);
401
402 assert_expected_uid_pid (flags, uid, pid);
403
404 g_clear_pointer (&tuple, g_variant_unref);
405
406 #ifdef HAVE_DBUS1
407 for (i = 0; i < n; i++)
408 {
409 LibdbusCall libdbus_call = { DBUS_ERROR_INIT, NULL, NULL, NULL };
410 GTask *task;
411
412 /* The test suite uses %G_TEST_OPTION_ISOLATE_DIRS, which sets
413 * `HOME=/dev/null` and leaves g_get_home_dir() pointing to the per-test
414 * temp home directory. Unfortunately, libdbus doesn’t allow the home dir
415 * to be overridden except using the environment, so copy the per-test
416 * temp home directory back there so that libdbus uses the same
417 * `$HOME/.dbus-keyrings` path as GLib. This is not thread-safe. */
418 g_setenv ("HOME", g_get_home_dir (), TRUE);
419
420 libdbus_call.conn = dbus_connection_open_private (connectable_address,
421 &libdbus_call.error);
422 g_assert_cmpstr (libdbus_call.error.name, ==, NULL);
423 g_assert_nonnull (libdbus_call.conn);
424
425 libdbus_call.call = dbus_message_new_method_call (NULL, "/",
426 "com.example.Test",
427 "WhoAmI");
428
429 if (libdbus_call.call == NULL)
430 g_error ("Out of memory");
431
432 result = NULL;
433 task = g_task_new (NULL, NULL, store_result_cb, &result);
434 g_task_set_task_data (task, &libdbus_call, NULL);
435 g_task_run_in_thread (task, libdbus_call_task_cb);
436
437 while (result == NULL)
438 g_main_context_iteration (NULL, TRUE);
439
440 g_clear_object (&result);
441
442 g_assert_cmpstr (libdbus_call.error.name, ==, NULL);
443 g_assert_nonnull (libdbus_call.reply);
444
445 uid = -2;
446 pid = -2;
447 dbus_message_get_args (libdbus_call.reply, &libdbus_call.error,
448 DBUS_TYPE_INT64, &uid,
449 DBUS_TYPE_INT64, &pid,
450 DBUS_TYPE_INVALID);
451 g_assert_cmpstr (libdbus_call.error.name, ==, NULL);
452
453 g_debug ("Server says libdbus client %" G_GSIZE_FORMAT " is uid %" G_GINT64_FORMAT ", pid %" G_GINT64_FORMAT,
454 i, uid, pid);
455 assert_expected_uid_pid (flags | INTEROP_FLAGS_LIBDBUS, uid, pid);
456
457 dbus_connection_close (libdbus_call.conn);
458 dbus_connection_unref (libdbus_call.conn);
459 dbus_message_unref (libdbus_call.call);
460 dbus_message_unref (libdbus_call.reply);
461 g_clear_object (&task);
462 }
463 #else /* !HAVE_DBUS1 */
464 g_test_skip ("Testing interop with libdbus not supported");
465 #endif /* !HAVE_DBUS1 */
466
467 /* No practical effect, just to avoid -Wunused-label under some
468 * combinations of #ifdefs */
469 goto out;
470
471 out:
472 if (server != NULL)
473 g_dbus_server_stop (server);
474
475 if (tmpdir != NULL)
476 g_assert_cmpstr (g_rmdir (tmpdir) == 0 ? "OK" : g_strerror (errno),
477 ==, "OK");
478
479 g_clear_object (&server);
480 g_clear_object (&observer);
481 g_free (guid);
482 g_free (listenable_address);
483 g_free (tmpdir);
484 }
485
486 static void
487 test_server_auth (void)
488 {
489 do_test_server_auth (INTEROP_FLAGS_NONE);
490 }
491
492 static void
493 test_server_auth_abstract (void)
494 {
495 do_test_server_auth (INTEROP_FLAGS_ABSTRACT);
496 }
497
498 static void
499 test_server_auth_tcp (void)
500 {
501 do_test_server_auth (INTEROP_FLAGS_TCP);
502 }
503
504 static void
505 test_server_auth_anonymous (void)
506 {
507 do_test_server_auth (INTEROP_FLAGS_ANONYMOUS);
508 }
509
510 static void
511 test_server_auth_anonymous_tcp (void)
512 {
513 do_test_server_auth (INTEROP_FLAGS_ANONYMOUS | INTEROP_FLAGS_TCP);
514 }
515
516 static void
517 test_server_auth_external (void)
518 {
519 do_test_server_auth (INTEROP_FLAGS_EXTERNAL);
520 }
521
522 static void
523 test_server_auth_external_require_same_user (void)
524 {
525 do_test_server_auth (INTEROP_FLAGS_EXTERNAL | INTEROP_FLAGS_REQUIRE_SAME_USER);
526 }
527
528 static void
529 test_server_auth_sha1 (void)
530 {
531 do_test_server_auth (INTEROP_FLAGS_SHA1);
532 }
533
534 static void
535 test_server_auth_sha1_tcp (void)
536 {
537 do_test_server_auth (INTEROP_FLAGS_SHA1 | INTEROP_FLAGS_TCP);
538 }
539
540 int
541 main (int argc,
542 char *argv[])
543 {
544 g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
545
546 g_test_add_func ("/gdbus/server-auth", test_server_auth);
547 g_test_add_func ("/gdbus/server-auth/abstract", test_server_auth_abstract);
548 g_test_add_func ("/gdbus/server-auth/tcp", test_server_auth_tcp);
549 g_test_add_func ("/gdbus/server-auth/anonymous", test_server_auth_anonymous);
550 g_test_add_func ("/gdbus/server-auth/anonymous/tcp", test_server_auth_anonymous_tcp);
551 g_test_add_func ("/gdbus/server-auth/external", test_server_auth_external);
552 g_test_add_func ("/gdbus/server-auth/external/require-same-user", test_server_auth_external_require_same_user);
553 g_test_add_func ("/gdbus/server-auth/sha1", test_server_auth_sha1);
554 g_test_add_func ("/gdbus/server-auth/sha1/tcp", test_server_auth_sha1_tcp);
555
556 return g_test_run();
557 }