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

source code of gtk/subprojects/glib/gio/tests/gdbus-server-auth.c