(root)/
glib-2.79.0/
gio/
tests/
gdbus-server-auth.c
       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  }