diff --git a/pressure-vessel/launcher.c b/pressure-vessel/launcher.c index 252d5d5a4e3a3a44819932ae411e588f9fe60997..37e3b31faaeee2a1d03e7097fc1591fb6065f37c 100644 --- a/pressure-vessel/launcher.c +++ b/pressure-vessel/launcher.c @@ -50,21 +50,10 @@ #include "portal-listener.h" #include "utils.h" -typedef GCredentials AutoCredentials; -G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoCredentials, g_object_unref) - -typedef GDBusAuthObserver AutoDBusAuthObserver; -G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusAuthObserver, g_object_unref) - -typedef GDBusServer AutoDBusServer; -G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusServer, g_object_unref) - static PvPortalListener *global_listener; static GHashTable *lock_env_hash = NULL; -static GDBusConnection *session_bus = NULL; static GHashTable *client_pid_data_hash = NULL; -static guint name_owner_id = 0; static GMainLoop *main_loop; static PvLauncher1 *launcher; @@ -85,10 +74,7 @@ unref_skeleton_in_timeout_cb (gpointer user_data) static void unref_skeleton_in_timeout (void) { - if (name_owner_id) - g_bus_unown_name (name_owner_id); - - name_owner_id = 0; + pv_portal_listener_release_name (global_listener); /* After we've lost the name we drop the main ref on the helper so that we'll exit when it drops to zero. However, if there are @@ -578,9 +564,9 @@ export_launcher (GDBusConnection *connection, } static void -on_bus_acquired (GDBusConnection *connection, - const gchar *name, - gpointer user_data) +on_bus_acquired (PvPortalListener *listener, + GDBusConnection *connection, + gpointer user_data) { g_autoptr(GError) error = NULL; int *ret = user_data; @@ -607,9 +593,10 @@ on_bus_acquired (GDBusConnection *connection, } static void -on_name_acquired (GDBusConnection *connection, - const gchar *name, - gpointer user_data) +on_name_acquired (PvPortalListener *listener, + GDBusConnection *connection, + const gchar *name, + gpointer user_data) { int *ret = user_data; @@ -624,93 +611,15 @@ on_name_acquired (GDBusConnection *connection, } static void -on_name_lost (GDBusConnection *connection, - const gchar *name, - gpointer user_data) +on_name_lost (PvPortalListener *listener, + GDBusConnection *connection, + const gchar *name, + gpointer user_data) { g_debug ("Name lost"); unref_skeleton_in_timeout (); } -#if GLIB_CHECK_VERSION(2, 34, 0) -/* - * Callback for GDBusServer::allow-mechanism. - * Only allow the (most secure) EXTERNAL authentication mechanism, - * if possible. - */ -static gboolean -allow_external_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, - const char *mechanism, - G_GNUC_UNUSED gpointer user_data) -{ - return (g_strcmp0 (mechanism, "EXTERNAL") == 0); -} -#endif - -/* - * Callback for GDBusServer::authorize-authenticated-peer. - * Only allow D-Bus connections from a matching uid. - */ -static gboolean -authorize_authenticated_peer_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, - G_GNUC_UNUSED GIOStream *stream, - GCredentials *credentials, - G_GNUC_UNUSED gpointer user_data) -{ - g_autoptr(AutoCredentials) retry_credentials = NULL; - g_autoptr(AutoCredentials) myself = NULL; - g_autofree gchar *credentials_str = NULL; - - if (g_credentials_get_unix_user (credentials, NULL) == (uid_t) -1) - { - /* Work around https://gitlab.gnome.org/GNOME/glib/issues/1831: - * older GLib versions might retrieve incomplete credentials. - * Make them try again. */ - credentials = NULL; - } - - if (credentials == NULL && G_IS_SOCKET_CONNECTION (stream)) - { - /* Continue to work around - * https://gitlab.gnome.org/GNOME/glib/issues/1831: - * older GLib versions might retrieve incomplete or no credentials. */ - GSocket *sock; - - sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream)); - retry_credentials = g_socket_get_credentials (sock, NULL); - credentials = retry_credentials; - } - - myself = g_credentials_new (); - - if (credentials != NULL && - g_credentials_is_same_user (credentials, myself, NULL)) - return TRUE; - - if (credentials == NULL) - credentials_str = g_strdup ("peer with unknown credentials"); - else - credentials_str = g_credentials_to_string (credentials); - - g_warning ("Rejecting connection from %s", credentials_str); - return FALSE; -} - -static GDBusAuthObserver * -observer_new (void) -{ - g_autoptr(AutoDBusAuthObserver) observer = g_dbus_auth_observer_new (); - -#if GLIB_CHECK_VERSION(2, 34, 0) - g_signal_connect (observer, "allow-mechanism", - G_CALLBACK (allow_external_cb), NULL); -#endif - g_signal_connect (observer, "authorize-authenticated-peer", - G_CALLBACK (authorize_authenticated_peer_cb), NULL); - - return g_steal_pointer (&observer); -} - /* * Callback for GDBusConnection::closed. */ @@ -724,67 +633,12 @@ peer_connection_closed_cb (GDBusConnection *connection, g_object_unref (connection); } -/* - * Double-check credentials of a peer, since we are working with older - * versions of GLib that can't necessarily be completely relied on. - * We are willing to execute arbitrary code on behalf of an authenticated - * connection, so it seems worthwhile to be extra-careful. - */ -static gboolean -check_credentials (GDBusConnection *connection, - GError **error) -{ - struct ucred creds; - socklen_t len; - GIOStream *stream; - GSocket *sock; - int sockfd; - - stream = g_dbus_connection_get_stream (connection); - len = sizeof (creds); - - if (!G_IS_SOCKET_CONNECTION (stream)) - return glnx_throw (error, "Incoming D-Bus connection is not a socket?"); - - sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream)); - - if (sock == NULL) - return glnx_throw (error, - "Incoming D-Bus connection does not have a socket?"); - - sockfd = g_socket_get_fd (sock); - - if (getsockopt (sockfd, SOL_SOCKET, SO_PEERCRED, &creds, &len) < 0) - return glnx_throw_errno_prefix (error, "Unable to check credentials"); - - if (creds.uid != geteuid ()) - return glnx_throw (error, - "Connection from uid %ld != %ld should have been " - "rejected already", - (long) creds.uid, (long) geteuid ()); - - return TRUE; -} - -/* - * Callback for GDBusServer::new-connection. - * - * Returns: %TRUE if the new connection attempt was handled, even if - * unsuccessfully. - */ static gboolean -new_connection_cb (GDBusServer *server, +new_connection_cb (PvPortalListener *listener, GDBusConnection *connection, gpointer user_data) { - g_autoptr(GError) error = NULL; - - if (!check_credentials (connection, &error)) - { - g_warning ("Credentials verification failed: %s", error->message); - g_dbus_connection_close (connection, NULL, NULL, NULL); - return TRUE; /* handled, unsuccessfully */ - } + GError *error = NULL; /* Paired with g_object_unref() in peer_connection_closed_cb() */ g_object_ref (connection); @@ -801,62 +655,6 @@ new_connection_cb (GDBusServer *server, return G_DBUS_METHOD_INVOCATION_HANDLED; } -static GDBusServer * -listen_on_address (const char *address, - GError **error) -{ - g_autofree gchar *guid = NULL; - g_autoptr(AutoDBusAuthObserver) observer = NULL; - g_autoptr(AutoDBusServer) server = NULL; - - guid = g_dbus_generate_guid (); - - observer = observer_new (); - - server = g_dbus_server_new_sync (address, - G_DBUS_SERVER_FLAGS_NONE, - guid, - observer, - NULL, - error); - - if (server == NULL) - return NULL; - - g_signal_connect (server, "new-connection", - G_CALLBACK (new_connection_cb), NULL); - g_dbus_server_start (server); - - return g_steal_pointer (&server); -} - -static GDBusServer * -listen_on_socket (const char *name, - GError **error) -{ - g_autofree gchar *address = NULL; - g_autofree gchar *escaped = NULL; - - if (name[0] == '@') - { - escaped = g_dbus_address_escape_value (&name[1]); - address = g_strdup_printf ("unix:abstract=%s", escaped); - } - else if (name[0] == '/') - { - escaped = g_dbus_address_escape_value (name); - address = g_strdup_printf ("unix:path=%s", escaped); - } - else - { - g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, - "Invalid socket address '%s'", name); - return FALSE; - } - - return listen_on_address (address, error); -} - static gboolean signal_handler (int sfd, G_GNUC_UNUSED GIOCondition condition, @@ -1090,13 +888,13 @@ int main (int argc, char *argv[]) { - g_autoptr(AutoDBusServer) server = NULL; g_autoptr(GOptionContext) context = NULL; guint signals_id = 0; guint exit_on_readable_id = 0; g_autoptr(GError) local_error = NULL; GError **error = &local_error; int ret = EX_USAGE; + GBusNameOwnerFlags flags; my_pid = getpid (); @@ -1219,108 +1017,39 @@ main (int argc, /* Exit with this status until we know otherwise */ ret = EX_SOFTWARE; - if (opt_bus_name != NULL) - { - GBusNameOwnerFlags flags; - - g_debug ("Connecting to D-Bus session bus..."); - - session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); - - if (session_bus == NULL) - { - glnx_prefix_error (error, "Can't find session bus"); - goto out; - } - - flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; - if (opt_replace) - flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; - - ret = EX_UNAVAILABLE; - g_debug ("Claiming bus name %s...", opt_bus_name); - name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, - opt_bus_name, - flags, - on_bus_acquired, - on_name_acquired, - on_name_lost, - &ret, - NULL); - } - else if (opt_socket != NULL) - { - g_debug ("Listening on socket %s...", opt_socket); - server = listen_on_socket (opt_socket, error); - - if (server == NULL) - { - glnx_prefix_error (error, - "Unable to listen on socket \"%s\"", opt_socket); - goto out; - } - - ret = 0; - } - else if (opt_socket_directory != NULL) - { - g_autofree gchar *unique = NULL; - g_autofree gchar *dir = NULL; - - if (strlen (opt_socket_directory) > PV_MAX_SOCKET_DIRECTORY_LEN) - { - glnx_throw (error, "Socket directory path \"%s\" too long", - opt_socket_directory); - goto out; - } - - dir = realpath (opt_socket_directory, NULL); - - if (strlen (dir) > PV_MAX_SOCKET_DIRECTORY_LEN) - { - glnx_throw (error, "Socket directory path \"%s\" too long", dir); - goto out; - } - - g_debug ("Listening on a socket in %s...", opt_socket_directory); - - /* @unique is long and random, so we assume it is not guessable - * by an attacker seeking to deny service by using the name we - * intended to use; so we don't need a retry loop for alternative - * names in the same directory. */ - unique = pv_get_random_uuid (error); - - if (unique == NULL) - goto out; - - opt_socket = g_build_filename (dir, unique, NULL); - g_debug ("Chosen socket is %s", opt_socket); - server = listen_on_socket (opt_socket, error); - - if (server == NULL) - { - glnx_prefix_error (error, - "Unable to listen on socket \"%s\"", opt_socket); - goto out; - } + g_signal_connect (global_listener, + "new-peer-connection", G_CALLBACK (new_connection_cb), + NULL); + g_signal_connect (global_listener, + "session-bus-connected", G_CALLBACK (on_bus_acquired), + &ret); + g_signal_connect (global_listener, + "session-bus-name-acquired", G_CALLBACK (on_name_acquired), + &ret); + g_signal_connect (global_listener, + "session-bus-name-lost", G_CALLBACK (on_name_lost), + NULL); + + flags = G_BUS_NAME_OWNER_FLAGS_ALLOW_REPLACEMENT; + + if (opt_replace) + flags |= G_BUS_NAME_OWNER_FLAGS_REPLACE; + + if (!pv_portal_listener_listen (global_listener, + opt_bus_name, + flags, + opt_socket, + opt_socket_directory, + error)) + goto out; - ret = 0; - } + /* If we're using the bus name method, we can't exit successfully + * until we claimed the bus name at least once. Otherwise we're + * already content. */ + if (opt_bus_name != NULL) + ret = EX_UNAVAILABLE; else - { - /* We already checked that exactly one of them is non-NULL */ - g_assert_not_reached (); - } - - if (opt_socket != NULL) - fprintf (global_listener->info_fh, "socket=%s\n", opt_socket); - - if (server != NULL) - fprintf (global_listener->info_fh, "dbus_address=%s\n", - g_dbus_server_get_client_address (server)); - - if (opt_bus_name == NULL) - pv_portal_listener_close_info_fh (global_listener, NULL); + ret = 0; g_debug ("Entering main loop"); @@ -1337,16 +1066,9 @@ out: if (signals_id > 0) g_source_remove (signals_id); - if (name_owner_id) - g_bus_unown_name (name_owner_id); - - if (server != NULL && opt_socket != NULL) - unlink (opt_socket); - g_free (opt_bus_name); g_free (opt_socket); g_free (opt_socket_directory); - g_clear_object (&session_bus); g_clear_object (&global_listener); g_hash_table_destroy (lock_env_hash); diff --git a/pressure-vessel/portal-listener.c b/pressure-vessel/portal-listener.c index d75af840fed4aa773835de7e834376c8a311ff6a..bff5152c7fb221f066e522d31b826cf7d6c5d65f 100644 --- a/pressure-vessel/portal-listener.c +++ b/pressure-vessel/portal-listener.c @@ -27,20 +27,58 @@ #include "config.h" #include "subprojects/libglnx/config.h" +#include <errno.h> +#include <locale.h> +#include <stdio.h> +#include <sysexits.h> +#include <sys/ioctl.h> +#include <sys/signalfd.h> +#include <sys/socket.h> + +#include <glib.h> +#include <glib-unix.h> +#include <gio/gio.h> +#include <gio/gunixfdlist.h> + #include "steam-runtime-tools/glib-backports-internal.h" #include "steam-runtime-tools/utils-internal.h" #include "libglnx/libglnx.h" +#include "launcher.h" #include "portal-listener.h" #include "utils.h" +typedef GCredentials AutoCredentials; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoCredentials, g_object_unref) + +typedef GDBusAuthObserver AutoDBusAuthObserver; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusAuthObserver, g_object_unref) + +typedef GDBusServer AutoDBusServer; +G_DEFINE_AUTOPTR_CLEANUP_FUNC(AutoDBusServer, g_object_unref) + struct _PvPortalListenerClass { GObjectClass parent; + + gboolean (*new_peer_connection) (PvPortalListener *self, + GDBusConnection *connection, + gpointer user_data); }; G_DEFINE_TYPE (PvPortalListener, pv_portal_listener, G_TYPE_OBJECT) +enum +{ + NEW_PEER_CONNECTION, + SESSION_BUS_CONNECTED, + SESSION_BUS_NAME_ACQUIRED, + SESSION_BUS_NAME_LOST, + N_SIGNALS +}; + +static guint signal_ids[N_SIGNALS] = { 0 }; + void pv_portal_listener_init (PvPortalListener *self) { @@ -123,6 +161,343 @@ pv_portal_listener_check_socket_arguments (PvPortalListener *listener, return TRUE; } +static void +on_bus_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + PvPortalListener *self = user_data; + + g_signal_emit (self, signal_ids[SESSION_BUS_CONNECTED], 0, connection); +} + +static void +on_name_acquired (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + PvPortalListener *self = user_data; + + g_signal_emit (self, signal_ids[SESSION_BUS_NAME_ACQUIRED], 0, + connection, name); +} + +static void +on_name_lost (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + PvPortalListener *self = user_data; + + g_signal_emit (self, signal_ids[SESSION_BUS_NAME_LOST], 0, + connection, name); +} + +#if GLIB_CHECK_VERSION(2, 34, 0) +/* + * Callback for GDBusServer::allow-mechanism. + * Only allow the (most secure) EXTERNAL authentication mechanism, + * if possible. + */ +static gboolean +allow_external_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, + const char *mechanism, + G_GNUC_UNUSED gpointer user_data) +{ + return (g_strcmp0 (mechanism, "EXTERNAL") == 0); +} +#endif + +/* + * Callback for GDBusServer::authorize-authenticated-peer. + * Only allow D-Bus connections from a matching uid. + */ +static gboolean +authorize_authenticated_peer_cb (G_GNUC_UNUSED GDBusAuthObserver *observer, + G_GNUC_UNUSED GIOStream *stream, + GCredentials *credentials, + G_GNUC_UNUSED gpointer user_data) +{ + g_autoptr(AutoCredentials) retry_credentials = NULL; + g_autoptr(AutoCredentials) myself = NULL; + g_autofree gchar *credentials_str = NULL; + + if (g_credentials_get_unix_user (credentials, NULL) == (uid_t) -1) + { + /* Work around https://gitlab.gnome.org/GNOME/glib/issues/1831: + * older GLib versions might retrieve incomplete credentials. + * Make them try again. */ + credentials = NULL; + } + + if (credentials == NULL && G_IS_SOCKET_CONNECTION (stream)) + { + /* Continue to work around + * https://gitlab.gnome.org/GNOME/glib/issues/1831: + * older GLib versions might retrieve incomplete or no credentials. */ + GSocket *sock; + + sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream)); + retry_credentials = g_socket_get_credentials (sock, NULL); + credentials = retry_credentials; + } + + myself = g_credentials_new (); + + if (credentials != NULL && + g_credentials_is_same_user (credentials, myself, NULL)) + return TRUE; + + if (credentials == NULL) + credentials_str = g_strdup ("peer with unknown credentials"); + else + credentials_str = g_credentials_to_string (credentials); + + g_warning ("Rejecting connection from %s", credentials_str); + return FALSE; +} + +static GDBusAuthObserver * +observer_new (void) +{ + g_autoptr(AutoDBusAuthObserver) observer = g_dbus_auth_observer_new (); + +#if GLIB_CHECK_VERSION(2, 34, 0) + g_signal_connect (observer, "allow-mechanism", + G_CALLBACK (allow_external_cb), NULL); +#endif + g_signal_connect (observer, "authorize-authenticated-peer", + G_CALLBACK (authorize_authenticated_peer_cb), NULL); + + return g_steal_pointer (&observer); +} + +/* + * Double-check credentials of a peer, since we are working with older + * versions of GLib that can't necessarily be completely relied on. + * We are willing to execute arbitrary code on behalf of an authenticated + * connection, so it seems worthwhile to be extra-careful. + */ +static gboolean +check_credentials (GDBusConnection *connection, + GError **error) +{ + struct ucred creds; + socklen_t len; + GIOStream *stream; + GSocket *sock; + int sockfd; + + stream = g_dbus_connection_get_stream (connection); + len = sizeof (creds); + + if (!G_IS_SOCKET_CONNECTION (stream)) + return glnx_throw (error, "Incoming D-Bus connection is not a socket?"); + + sock = g_socket_connection_get_socket (G_SOCKET_CONNECTION (stream)); + + if (sock == NULL) + return glnx_throw (error, + "Incoming D-Bus connection does not have a socket?"); + + sockfd = g_socket_get_fd (sock); + + if (getsockopt (sockfd, SOL_SOCKET, SO_PEERCRED, &creds, &len) < 0) + return glnx_throw_errno_prefix (error, "Unable to check credentials"); + + if (creds.uid != geteuid ()) + return glnx_throw (error, + "Connection from uid %ld != %ld should have been " + "rejected already", + (long) creds.uid, (long) geteuid ()); + + return TRUE; +} + +/* + * Callback for GDBusServer::new-connection. + * + * Returns: %TRUE if the new connection attempt was handled, even if + * unsuccessfully. + */ +static gboolean +new_connection_cb (GDBusServer *server, + GDBusConnection *connection, + gpointer user_data) +{ + PvPortalListener *self = user_data; + g_autoptr(GError) error = NULL; + gboolean handled; + + g_return_val_if_fail (self->server == server, FALSE); + + if (!check_credentials (connection, &error)) + { + g_warning ("Credentials verification failed: %s", error->message); + g_dbus_connection_close (connection, NULL, NULL, NULL); + return TRUE; /* handled, unsuccessfully */ + } + + g_signal_emit (self, signal_ids[NEW_PEER_CONNECTION], 0, connection, + &handled); + g_return_val_if_fail (handled, FALSE); + return TRUE; +} + +static GDBusServer * +listen_on_address (PvPortalListener *self, + const char *address, + GError **error) +{ + g_autofree gchar *guid = NULL; + g_autoptr(AutoDBusAuthObserver) observer = NULL; + g_autoptr(AutoDBusServer) server = NULL; + + guid = g_dbus_generate_guid (); + + observer = observer_new (); + + server = g_dbus_server_new_sync (address, + G_DBUS_SERVER_FLAGS_NONE, + guid, + observer, + NULL, + error); + + if (server == NULL) + return NULL; + + g_signal_connect (server, "new-connection", + G_CALLBACK (new_connection_cb), self); + g_dbus_server_start (server); + + return g_steal_pointer (&server); +} + +static GDBusServer * +listen_on_socket (PvPortalListener *self, + GError **error) +{ + g_autofree gchar *address = NULL; + g_autofree gchar *escaped = NULL; + + if (self->server_socket[0] == '@') + { + escaped = g_dbus_address_escape_value (&self->server_socket[1]); + address = g_strdup_printf ("unix:abstract=%s", escaped); + } + else if (self->server_socket[0] == '/') + { + escaped = g_dbus_address_escape_value (self->server_socket); + address = g_strdup_printf ("unix:path=%s", escaped); + } + else + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid socket address '%s'", self->server_socket); + return FALSE; + } + + return listen_on_address (self, address, error); +} + +gboolean +pv_portal_listener_listen (PvPortalListener *self, + const char *opt_bus_name, + GBusNameOwnerFlags flags, + const char *opt_socket, + const char *opt_socket_directory, + GError **error) +{ + g_return_val_if_fail ((opt_bus_name != NULL) + + (opt_socket != NULL) + + (opt_socket_directory != NULL) == 1, FALSE); + + if (opt_bus_name != NULL) + { + g_debug ("Connecting to D-Bus session bus..."); + + self->session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error); + + if (self->session_bus == NULL) + return glnx_prefix_error (error, "Can't find session bus"); + + g_debug ("Claiming bus name %s...", opt_bus_name); + self->name_owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, + opt_bus_name, + flags, + on_bus_acquired, + on_name_acquired, + on_name_lost, + self, + NULL); + } + else if (opt_socket != NULL) + { + g_debug ("Listening on socket %s...", opt_socket); + self->server_socket = g_strdup (opt_socket); + self->server = listen_on_socket (self, error); + + if (self->server == NULL) + return glnx_prefix_error (error, + "Unable to listen on socket \"%s\"", + opt_socket); + } + else if (opt_socket_directory != NULL) + { + g_autofree gchar *unique = NULL; + g_autofree gchar *dir = NULL; + + if (strlen (opt_socket_directory) > PV_MAX_SOCKET_DIRECTORY_LEN) + return glnx_throw (error, "Socket directory path \"%s\" too long", + opt_socket_directory); + + dir = realpath (opt_socket_directory, NULL); + + if (strlen (dir) > PV_MAX_SOCKET_DIRECTORY_LEN) + return glnx_throw (error, + "Socket directory path \"%s\" too long", + dir); + + g_debug ("Listening on a socket in %s...", opt_socket_directory); + + /* @unique is long and random, so we assume it is not guessable + * by an attacker seeking to deny service by using the name we + * intended to use; so we don't need a retry loop for alternative + * names in the same directory. */ + unique = pv_get_random_uuid (error); + + if (unique == NULL) + return FALSE; + + self->server_socket = g_build_filename (dir, unique, NULL); + g_debug ("Chosen socket is %s", self->server_socket); + self->server = listen_on_socket (self, error); + + if (self->server == NULL) + return glnx_prefix_error (error, + "Unable to listen on socket \"%s\"", + self->server_socket); + } + else + { + g_return_val_if_reached (FALSE); + } + + if (self->server_socket != NULL) + fprintf (self->info_fh, "socket=%s\n", self->server_socket); + + if (self->server != NULL) + fprintf (self->info_fh, "dbus_address=%s\n", + g_dbus_server_get_client_address (self->server)); + + if (opt_bus_name == NULL) + pv_portal_listener_close_info_fh (self, NULL); + + return TRUE; +} + + /* * If @bus_name is non-NULL, print it to the info fd. Then * close the --info-fd, and also close standard output (if different). @@ -147,12 +522,28 @@ pv_portal_listener_close_info_fh (PvPortalListener *self, g_clear_pointer (&self->info_fh, fclose); } +void +pv_portal_listener_release_name (PvPortalListener *self) +{ + if (self->name_owner_id) + g_bus_unown_name (self->name_owner_id); + + self->name_owner_id = 0; +} + static void pv_portal_listener_dispose (GObject *object) { PvPortalListener *self = PV_PORTAL_LISTENER (object); pv_portal_listener_close_info_fh (self, NULL); + pv_portal_listener_release_name (self); + + if (self->server != NULL && self->server_socket != NULL) + unlink (self->server_socket); + + g_clear_object (&self->server); + g_clear_object (&self->session_bus); G_OBJECT_CLASS (pv_portal_listener_parent_class)->dispose (object); } @@ -162,6 +553,7 @@ pv_portal_listener_finalize (GObject *object) { PvPortalListener *self = PV_PORTAL_LISTENER (object); + g_clear_pointer (&self->server_socket, g_free); g_clear_pointer (&self->original_environ, g_strfreev); g_clear_pointer (&self->original_cwd_l, g_free); @@ -175,6 +567,52 @@ pv_portal_listener_class_init (PvPortalListenerClass *cls) object_class->dispose = pv_portal_listener_dispose; object_class->finalize = pv_portal_listener_finalize; + + /* + * Basically the same as GDBusServer::new-connection + */ + signal_ids[NEW_PEER_CONNECTION] = + g_signal_new ("new-peer-connection", + PV_TYPE_PORTAL_LISTENER, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (PvPortalListenerClass, new_peer_connection), + g_signal_accumulator_true_handled, NULL, + NULL, + G_TYPE_BOOLEAN, + 1, + G_TYPE_DBUS_CONNECTION); + + signal_ids[SESSION_BUS_CONNECTED] = + g_signal_new ("session-bus-connected", + PV_TYPE_PORTAL_LISTENER, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + NULL, + G_TYPE_NONE, + 1, + G_TYPE_DBUS_CONNECTION); + + signal_ids[SESSION_BUS_NAME_ACQUIRED] = + g_signal_new ("session-bus-name-acquired", + PV_TYPE_PORTAL_LISTENER, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_DBUS_CONNECTION, + G_TYPE_STRING); + + signal_ids[SESSION_BUS_NAME_LOST] = + g_signal_new ("session-bus-name-lost", + PV_TYPE_PORTAL_LISTENER, + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + NULL, + G_TYPE_NONE, + 2, + G_TYPE_DBUS_CONNECTION, + G_TYPE_STRING); } PvPortalListener * diff --git a/pressure-vessel/portal-listener.h b/pressure-vessel/portal-listener.h index cddb174eb39e3de9d1281d9eae35f297c64b282a..38b7cca5b5788b9b8ef46af30b8995212ec135c4 100644 --- a/pressure-vessel/portal-listener.h +++ b/pressure-vessel/portal-listener.h @@ -53,7 +53,11 @@ struct _PvPortalListener GStrv original_environ; FILE *original_stdout; FILE *info_fh; + GDBusConnection *session_bus; + GDBusServer *server; gchar *original_cwd_l; + gchar *server_socket; + guint name_owner_id; }; PvPortalListener *pv_portal_listener_new (void); @@ -68,7 +72,16 @@ gboolean pv_portal_listener_check_socket_arguments (PvPortalListener *self, const char *opt_socket_directory, GError **error); +gboolean pv_portal_listener_listen (PvPortalListener *self, + const char *opt_bus_name, + GBusNameOwnerFlags flags, + const char *opt_socket, + const char *opt_socket_directory, + GError **error); + void pv_portal_listener_close_info_fh (PvPortalListener *self, const char *bus_name); +void pv_portal_listener_release_name (PvPortalListener *self); + G_DEFINE_AUTOPTR_CLEANUP_FUNC (PvPortalListener, g_object_unref)