• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
32 typedef 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 
44 static gboolean
allow_external_cb(G_GNUC_UNUSED GDBusAuthObserver * observer,const char * mechanism,G_GNUC_UNUSED gpointer user_data)45 allow_external_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
46                    const char *mechanism,
47                    G_GNUC_UNUSED gpointer user_data)
48 {
49   if (g_strcmp0 (mechanism, "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 
61 static gboolean
allow_anonymous_cb(G_GNUC_UNUSED GDBusAuthObserver * observer,const char * mechanism,G_GNUC_UNUSED gpointer user_data)62 allow_anonymous_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
63                     const char *mechanism,
64                     G_GNUC_UNUSED gpointer user_data)
65 {
66   if (g_strcmp0 (mechanism, "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 
78 static gboolean
allow_sha1_cb(G_GNUC_UNUSED GDBusAuthObserver * observer,const char * mechanism,G_GNUC_UNUSED gpointer user_data)79 allow_sha1_cb (G_GNUC_UNUSED GDBusAuthObserver *observer,
80                const char *mechanism,
81                G_GNUC_UNUSED gpointer user_data)
82 {
83   if (g_strcmp0 (mechanism, "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 
96 static gboolean
allow_any_mechanism_cb(G_GNUC_UNUSED GDBusAuthObserver * observer,const char * mechanism,G_GNUC_UNUSED gpointer user_data)97 allow_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 
105 static gboolean
authorize_any_authenticated_peer_cb(G_GNUC_UNUSED GDBusAuthObserver * observer,G_GNUC_UNUSED GIOStream * stream,GCredentials * credentials,G_GNUC_UNUSED gpointer user_data)106 authorize_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 (str);
121     }
122 
123   return TRUE;
124 }
125 
126 static GDBusMessage *
whoami_filter_cb(GDBusConnection * connection,GDBusMessage * message,gboolean incoming,G_GNUC_UNUSED gpointer user_data)127 whoami_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 (g_dbus_message_get_member (message), "WhoAmI") == 0)
137     {
138       GDBusMessage *reply = g_dbus_message_new_method_reply (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 (reply,
152                                g_variant_new ("(xx)", uid, pid));
153       g_dbus_connection_send_message (connection, reply,
154                                       G_DBUS_SEND_MESSAGE_FLAGS_NONE,
155                                       NULL, NULL);
156       g_object_unref (reply);
157 
158       /* handled */
159       g_object_unref (message);
160       return NULL;
161     }
162 
163   return message;
164 }
165 
166 static gboolean
new_connection_cb(G_GNUC_UNUSED GDBusServer * server,GDBusConnection * connection,G_GNUC_UNUSED gpointer user_data)167 new_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 (str);
183     }
184 
185   g_object_ref (connection);
186   g_dbus_connection_add_filter (connection, whoami_filter_cb, NULL, NULL);
187   return TRUE;
188 }
189 
190 #ifdef HAVE_DBUS1
191 typedef struct
192 {
193   DBusError error;
194   DBusConnection *conn;
195   DBusMessage *call;
196   DBusMessage *reply;
197 } LibdbusCall;
198 
199 static void
libdbus_call_task_cb(GTask * task,G_GNUC_UNUSED gpointer source_object,gpointer task_data,G_GNUC_UNUSED GCancellable * cancellable)200 libdbus_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 
214 static void
store_result_cb(G_GNUC_UNUSED GObject * source_object,GAsyncResult * res,gpointer user_data)215 store_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 
226 static void
assert_expected_uid_pid(InteropFlags flags,gint64 uid,gint64 pid)227 assert_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 
261 static void
do_test_server_auth(InteropFlags flags)262 do_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 ("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 ("gdbus-server-auth-XXXXXX", &error);
293       g_assert_no_error (error);
294       escaped = g_dbus_address_escape_value (tmpdir);
295       listenable_address = g_strdup_printf ("unix:%s=%s",
296                                             (flags & INTEROP_FLAGS_ABSTRACT) ? "tmpdir" : "dir",
297                                             escaped);
298       g_free (escaped);
299 #else
300       g_test_skip ("unix: addresses only work on Unix");
301       goto out;
302 #endif
303     }
304 
305   g_test_message ("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 (listenable_address,
353                                    server_flags,
354                                    guid,
355                                    observer,
356                                    NULL,
357                                    &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 ("Connectable address: %s", connectable_address);
364 
365   result = NULL;
366   g_dbus_connection_new_for_address (connectable_address,
367                                      G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT,
368                                      NULL, NULL, store_result_cb, &result);
369 
370   while (result == NULL)
371     g_main_context_iteration (NULL, TRUE);
372 
373   client = g_dbus_connection_new_for_address_finish (result, &error);
374   g_assert_no_error (error);
375   g_assert_nonnull (client);
376   g_clear_object (&result);
377 
378   g_dbus_connection_call (client, NULL, "/", "com.example.Test", "WhoAmI",
379                           NULL, G_VARIANT_TYPE ("(xx)"),
380                           G_DBUS_CALL_FLAGS_NONE, -1, NULL, store_result_cb,
381                           &result);
382 
383   while (result == NULL)
384     g_main_context_iteration (NULL, TRUE);
385 
386   tuple = g_dbus_connection_call_finish (client, result, &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 (tuple, "(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 ("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 
468 out:
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 (guid);
479   g_free (listenable_address);
480   g_free (tmpdir);
481 }
482 
483 static void
test_server_auth(void)484 test_server_auth (void)
485 {
486   do_test_server_auth (INTEROP_FLAGS_NONE);
487 }
488 
489 static void
test_server_auth_abstract(void)490 test_server_auth_abstract (void)
491 {
492   do_test_server_auth (INTEROP_FLAGS_ABSTRACT);
493 }
494 
495 static void
test_server_auth_tcp(void)496 test_server_auth_tcp (void)
497 {
498   do_test_server_auth (INTEROP_FLAGS_TCP);
499 }
500 
501 static void
test_server_auth_anonymous(void)502 test_server_auth_anonymous (void)
503 {
504   do_test_server_auth (INTEROP_FLAGS_ANONYMOUS);
505 }
506 
507 static void
test_server_auth_anonymous_tcp(void)508 test_server_auth_anonymous_tcp (void)
509 {
510   do_test_server_auth (INTEROP_FLAGS_ANONYMOUS | INTEROP_FLAGS_TCP);
511 }
512 
513 static void
test_server_auth_external(void)514 test_server_auth_external (void)
515 {
516   do_test_server_auth (INTEROP_FLAGS_EXTERNAL);
517 }
518 
519 static void
test_server_auth_external_require_same_user(void)520 test_server_auth_external_require_same_user (void)
521 {
522   do_test_server_auth (INTEROP_FLAGS_EXTERNAL | INTEROP_FLAGS_REQUIRE_SAME_USER);
523 }
524 
525 static void
test_server_auth_sha1(void)526 test_server_auth_sha1 (void)
527 {
528   do_test_server_auth (INTEROP_FLAGS_SHA1);
529 }
530 
531 static void
test_server_auth_sha1_tcp(void)532 test_server_auth_sha1_tcp (void)
533 {
534   do_test_server_auth (INTEROP_FLAGS_SHA1 | INTEROP_FLAGS_TCP);
535 }
536 
537 int
main(int argc,char * argv[])538 main (int   argc,
539       char *argv[])
540 {
541   g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
542 
543   g_test_add_func ("/gdbus/server-auth", test_server_auth);
544   g_test_add_func ("/gdbus/server-auth/abstract", test_server_auth_abstract);
545   g_test_add_func ("/gdbus/server-auth/tcp", test_server_auth_tcp);
546   g_test_add_func ("/gdbus/server-auth/anonymous", test_server_auth_anonymous);
547   g_test_add_func ("/gdbus/server-auth/anonymous/tcp", test_server_auth_anonymous_tcp);
548   g_test_add_func ("/gdbus/server-auth/external", test_server_auth_external);
549   g_test_add_func ("/gdbus/server-auth/external/require-same-user", test_server_auth_external_require_same_user);
550   g_test_add_func ("/gdbus/server-auth/sha1", test_server_auth_sha1);
551   g_test_add_func ("/gdbus/server-auth/sha1/tcp", test_server_auth_sha1_tcp);
552 
553   return g_test_run();
554 }
555