mirror of
https://gitlab.gnome.org/GNOME/glib.git
synced 2024-12-26 23:46:15 +01:00
c0a1a3b384
This implements https://gitlab.freedesktop.org/dbus/dbus/-/merge_requests/350 for GDBus's server implementation. Abstract sockets belong to the network namespace instead of the mount namespace. As a result, mount namespace-based sandboxes (e.g. Flatpak) cannot restrict access to abstract sockets (and therefore GDBus's unix:tmpdir= server addresses), at least for applications with network access permission, which may result in sandbox escapes unless the application running the GDBus server explicitly check that the connecting process is not in a sandbox. As of the time of writing, no known applications using GDBusServer does this. Fix this by always using non-abstract sockets for unix:tmpdir=, which is allowed by the DBus specification.
1191 lines
37 KiB
C
1191 lines
37 KiB
C
/* GDBus - GLib D-Bus Library
|
|
*
|
|
* Copyright (C) 2008-2010 Red Hat, Inc.
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1-or-later
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General
|
|
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Author: David Zeuthen <davidz@redhat.com>
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "giotypes.h"
|
|
#include "gioerror.h"
|
|
#include "gdbusaddress.h"
|
|
#include "gdbusutils.h"
|
|
#include "gdbusconnection.h"
|
|
#include "gdbusserver.h"
|
|
#include "gioenumtypes.h"
|
|
#include "gdbusprivate.h"
|
|
#include "gdbusauthobserver.h"
|
|
#include "ginitable.h"
|
|
#include "gsocketservice.h"
|
|
#include "gthreadedsocketservice.h"
|
|
#include "gresolver.h"
|
|
#include "glib/gstdio.h"
|
|
#include "ginetaddress.h"
|
|
#include "ginetsocketaddress.h"
|
|
#include "ginputstream.h"
|
|
#include "giostream.h"
|
|
#include "gmarshal-internal.h"
|
|
|
|
#ifdef G_OS_UNIX
|
|
#include <unistd.h>
|
|
#endif
|
|
#ifdef G_OS_WIN32
|
|
#include <io.h>
|
|
#endif
|
|
|
|
#include "gunixsocketaddress.h"
|
|
|
|
#include "glibintl.h"
|
|
|
|
#define G_DBUS_SERVER_FLAGS_ALL \
|
|
(G_DBUS_SERVER_FLAGS_RUN_IN_THREAD | \
|
|
G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS | \
|
|
G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER)
|
|
|
|
/**
|
|
* SECTION:gdbusserver
|
|
* @short_description: Helper for accepting connections
|
|
* @include: gio/gio.h
|
|
*
|
|
* #GDBusServer is a helper for listening to and accepting D-Bus
|
|
* connections. This can be used to create a new D-Bus server, allowing two
|
|
* peers to use the D-Bus protocol for their own specialized communication.
|
|
* A server instance provided in this way will not perform message routing or
|
|
* implement the org.freedesktop.DBus interface.
|
|
*
|
|
* To just export an object on a well-known name on a message bus, such as the
|
|
* session or system bus, you should instead use g_bus_own_name().
|
|
*
|
|
* An example of peer-to-peer communication with GDBus can be found
|
|
* in [gdbus-example-peer.c](https://gitlab.gnome.org/GNOME/glib/-/blob/HEAD/gio/tests/gdbus-example-peer.c).
|
|
*
|
|
* Note that a minimal #GDBusServer will accept connections from any
|
|
* peer. In many use-cases it will be necessary to add a #GDBusAuthObserver
|
|
* that only accepts connections that have successfully authenticated
|
|
* as the same user that is running the #GDBusServer. Since GLib 2.68 this can
|
|
* be achieved more simply by passing the
|
|
* %G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER flag to the server.
|
|
*/
|
|
|
|
/**
|
|
* GDBusServer:
|
|
*
|
|
* The #GDBusServer structure contains only private data and
|
|
* should only be accessed using the provided API.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
struct _GDBusServer
|
|
{
|
|
/*< private >*/
|
|
GObject parent_instance;
|
|
|
|
GDBusServerFlags flags;
|
|
gchar *address;
|
|
gchar *guid;
|
|
|
|
guchar *nonce;
|
|
gchar *nonce_file;
|
|
|
|
gchar *client_address;
|
|
|
|
gchar *unix_socket_path;
|
|
GSocketListener *listener;
|
|
gboolean is_using_listener;
|
|
gulong run_signal_handler_id;
|
|
|
|
/* The result of g_main_context_ref_thread_default() when the object
|
|
* was created (the GObject _init() function) - this is used for delivery
|
|
* of the :new-connection GObject signal.
|
|
*/
|
|
GMainContext *main_context_at_construction;
|
|
|
|
gboolean active;
|
|
|
|
GDBusAuthObserver *authentication_observer;
|
|
};
|
|
|
|
typedef struct _GDBusServerClass GDBusServerClass;
|
|
|
|
/**
|
|
* GDBusServerClass:
|
|
* @new_connection: Signal class handler for the #GDBusServer::new-connection signal.
|
|
*
|
|
* Class structure for #GDBusServer.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
struct _GDBusServerClass
|
|
{
|
|
/*< private >*/
|
|
GObjectClass parent_class;
|
|
|
|
/*< public >*/
|
|
/* Signals */
|
|
gboolean (*new_connection) (GDBusServer *server,
|
|
GDBusConnection *connection);
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_ADDRESS,
|
|
PROP_CLIENT_ADDRESS,
|
|
PROP_FLAGS,
|
|
PROP_GUID,
|
|
PROP_ACTIVE,
|
|
PROP_AUTHENTICATION_OBSERVER,
|
|
};
|
|
|
|
enum
|
|
{
|
|
NEW_CONNECTION_SIGNAL,
|
|
LAST_SIGNAL,
|
|
};
|
|
|
|
static guint _signals[LAST_SIGNAL] = {0};
|
|
|
|
static void initable_iface_init (GInitableIface *initable_iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GDBusServer, g_dbus_server, G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
|
|
|
|
static void
|
|
g_dbus_server_dispose (GObject *object)
|
|
{
|
|
GDBusServer *server = G_DBUS_SERVER (object);
|
|
|
|
if (server->active)
|
|
g_dbus_server_stop (server);
|
|
|
|
G_OBJECT_CLASS (g_dbus_server_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
g_dbus_server_finalize (GObject *object)
|
|
{
|
|
GDBusServer *server = G_DBUS_SERVER (object);
|
|
|
|
g_assert (!server->active);
|
|
|
|
if (server->authentication_observer != NULL)
|
|
g_object_unref (server->authentication_observer);
|
|
|
|
if (server->run_signal_handler_id > 0)
|
|
g_signal_handler_disconnect (server->listener, server->run_signal_handler_id);
|
|
|
|
if (server->listener != NULL)
|
|
g_object_unref (server->listener);
|
|
|
|
g_free (server->address);
|
|
g_free (server->guid);
|
|
g_free (server->client_address);
|
|
if (server->nonce != NULL)
|
|
{
|
|
memset (server->nonce, '\0', 16);
|
|
g_free (server->nonce);
|
|
}
|
|
|
|
g_free (server->unix_socket_path);
|
|
g_free (server->nonce_file);
|
|
|
|
g_main_context_unref (server->main_context_at_construction);
|
|
|
|
G_OBJECT_CLASS (g_dbus_server_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
g_dbus_server_get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GDBusServer *server = G_DBUS_SERVER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_FLAGS:
|
|
g_value_set_flags (value, server->flags);
|
|
break;
|
|
|
|
case PROP_GUID:
|
|
g_value_set_string (value, server->guid);
|
|
break;
|
|
|
|
case PROP_ADDRESS:
|
|
g_value_set_string (value, server->address);
|
|
break;
|
|
|
|
case PROP_CLIENT_ADDRESS:
|
|
g_value_set_string (value, server->client_address);
|
|
break;
|
|
|
|
case PROP_ACTIVE:
|
|
g_value_set_boolean (value, server->active);
|
|
break;
|
|
|
|
case PROP_AUTHENTICATION_OBSERVER:
|
|
g_value_set_object (value, server->authentication_observer);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_dbus_server_set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
GDBusServer *server = G_DBUS_SERVER (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_FLAGS:
|
|
server->flags = g_value_get_flags (value);
|
|
break;
|
|
|
|
case PROP_GUID:
|
|
server->guid = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_ADDRESS:
|
|
server->address = g_value_dup_string (value);
|
|
break;
|
|
|
|
case PROP_AUTHENTICATION_OBSERVER:
|
|
server->authentication_observer = g_value_dup_object (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
g_dbus_server_class_init (GDBusServerClass *klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->dispose = g_dbus_server_dispose;
|
|
gobject_class->finalize = g_dbus_server_finalize;
|
|
gobject_class->set_property = g_dbus_server_set_property;
|
|
gobject_class->get_property = g_dbus_server_get_property;
|
|
|
|
/**
|
|
* GDBusServer:flags:
|
|
*
|
|
* Flags from the #GDBusServerFlags enumeration.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_FLAGS,
|
|
g_param_spec_flags ("flags",
|
|
P_("Flags"),
|
|
P_("Flags for the server"),
|
|
G_TYPE_DBUS_SERVER_FLAGS,
|
|
G_DBUS_SERVER_FLAGS_NONE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_BLURB |
|
|
G_PARAM_STATIC_NICK));
|
|
|
|
/**
|
|
* GDBusServer:guid:
|
|
*
|
|
* The GUID of the server.
|
|
*
|
|
* See #GDBusConnection:guid for more details.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_GUID,
|
|
g_param_spec_string ("guid",
|
|
P_("GUID"),
|
|
P_("The guid of the server"),
|
|
NULL,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_BLURB |
|
|
G_PARAM_STATIC_NICK));
|
|
|
|
/**
|
|
* GDBusServer:address:
|
|
*
|
|
* The D-Bus address to listen on.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_ADDRESS,
|
|
g_param_spec_string ("address",
|
|
P_("Address"),
|
|
P_("The address to listen on"),
|
|
NULL,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_BLURB |
|
|
G_PARAM_STATIC_NICK));
|
|
|
|
/**
|
|
* GDBusServer:client-address:
|
|
*
|
|
* The D-Bus address that clients can use.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_CLIENT_ADDRESS,
|
|
g_param_spec_string ("client-address",
|
|
P_("Client Address"),
|
|
P_("The address clients can use"),
|
|
NULL,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_BLURB |
|
|
G_PARAM_STATIC_NICK));
|
|
|
|
/**
|
|
* GDBusServer:active:
|
|
*
|
|
* Whether the server is currently active.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_ACTIVE,
|
|
g_param_spec_boolean ("active",
|
|
P_("Active"),
|
|
P_("Whether the server is currently active"),
|
|
FALSE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_BLURB |
|
|
G_PARAM_STATIC_NICK));
|
|
|
|
/**
|
|
* GDBusServer:authentication-observer:
|
|
*
|
|
* A #GDBusAuthObserver object to assist in the authentication process or %NULL.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_AUTHENTICATION_OBSERVER,
|
|
g_param_spec_object ("authentication-observer",
|
|
P_("Authentication Observer"),
|
|
P_("Object used to assist in the authentication process"),
|
|
G_TYPE_DBUS_AUTH_OBSERVER,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_WRITABLE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_NAME |
|
|
G_PARAM_STATIC_BLURB |
|
|
G_PARAM_STATIC_NICK));
|
|
|
|
/**
|
|
* GDBusServer::new-connection:
|
|
* @server: The #GDBusServer emitting the signal.
|
|
* @connection: A #GDBusConnection for the new connection.
|
|
*
|
|
* Emitted when a new authenticated connection has been made. Use
|
|
* g_dbus_connection_get_peer_credentials() to figure out what
|
|
* identity (if any), was authenticated.
|
|
*
|
|
* If you want to accept the connection, take a reference to the
|
|
* @connection object and return %TRUE. When you are done with the
|
|
* connection call g_dbus_connection_close() and give up your
|
|
* reference. Note that the other peer may disconnect at any time -
|
|
* a typical thing to do when accepting a connection is to listen to
|
|
* the #GDBusConnection::closed signal.
|
|
*
|
|
* If #GDBusServer:flags contains %G_DBUS_SERVER_FLAGS_RUN_IN_THREAD
|
|
* then the signal is emitted in a new thread dedicated to the
|
|
* connection. Otherwise the signal is emitted in the
|
|
* [thread-default main context][g-main-context-push-thread-default]
|
|
* of the thread that @server was constructed in.
|
|
*
|
|
* You are guaranteed that signal handlers for this signal runs
|
|
* before incoming messages on @connection are processed. This means
|
|
* that it's suitable to call g_dbus_connection_register_object() or
|
|
* similar from the signal handler.
|
|
*
|
|
* Returns: %TRUE to claim @connection, %FALSE to let other handlers
|
|
* run.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
_signals[NEW_CONNECTION_SIGNAL] = g_signal_new (I_("new-connection"),
|
|
G_TYPE_DBUS_SERVER,
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (GDBusServerClass, new_connection),
|
|
g_signal_accumulator_true_handled,
|
|
NULL, /* accu_data */
|
|
_g_cclosure_marshal_BOOLEAN__OBJECT,
|
|
G_TYPE_BOOLEAN,
|
|
1,
|
|
G_TYPE_DBUS_CONNECTION);
|
|
g_signal_set_va_marshaller (_signals[NEW_CONNECTION_SIGNAL],
|
|
G_TYPE_FROM_CLASS (klass),
|
|
_g_cclosure_marshal_BOOLEAN__OBJECTv);
|
|
}
|
|
|
|
static void
|
|
g_dbus_server_init (GDBusServer *server)
|
|
{
|
|
server->main_context_at_construction = g_main_context_ref_thread_default ();
|
|
}
|
|
|
|
static gboolean
|
|
on_run (GSocketService *service,
|
|
GSocketConnection *socket_connection,
|
|
GObject *source_object,
|
|
gpointer user_data);
|
|
|
|
/**
|
|
* g_dbus_server_new_sync:
|
|
* @address: A D-Bus address.
|
|
* @flags: Flags from the #GDBusServerFlags enumeration.
|
|
* @guid: A D-Bus GUID.
|
|
* @observer: (nullable): A #GDBusAuthObserver or %NULL.
|
|
* @cancellable: (nullable): A #GCancellable or %NULL.
|
|
* @error: Return location for server or %NULL.
|
|
*
|
|
* Creates a new D-Bus server that listens on the first address in
|
|
* @address that works.
|
|
*
|
|
* Once constructed, you can use g_dbus_server_get_client_address() to
|
|
* get a D-Bus address string that clients can use to connect.
|
|
*
|
|
* To have control over the available authentication mechanisms and
|
|
* the users that are authorized to connect, it is strongly recommended
|
|
* to provide a non-%NULL #GDBusAuthObserver.
|
|
*
|
|
* Connect to the #GDBusServer::new-connection signal to handle
|
|
* incoming connections.
|
|
*
|
|
* The returned #GDBusServer isn't active - you have to start it with
|
|
* g_dbus_server_start().
|
|
*
|
|
* #GDBusServer is used in this [example][gdbus-peer-to-peer].
|
|
*
|
|
* This is a synchronous failable constructor. There is currently no
|
|
* asynchronous version.
|
|
*
|
|
* Returns: A #GDBusServer or %NULL if @error is set. Free with
|
|
* g_object_unref().
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
GDBusServer *
|
|
g_dbus_server_new_sync (const gchar *address,
|
|
GDBusServerFlags flags,
|
|
const gchar *guid,
|
|
GDBusAuthObserver *observer,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GDBusServer *server;
|
|
|
|
g_return_val_if_fail (address != NULL, NULL);
|
|
g_return_val_if_fail (g_dbus_is_guid (guid), NULL);
|
|
g_return_val_if_fail ((flags & ~G_DBUS_SERVER_FLAGS_ALL) == 0, NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
server = g_initable_new (G_TYPE_DBUS_SERVER,
|
|
cancellable,
|
|
error,
|
|
"address", address,
|
|
"flags", flags,
|
|
"guid", guid,
|
|
"authentication-observer", observer,
|
|
NULL);
|
|
|
|
return server;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_server_get_client_address:
|
|
* @server: A #GDBusServer.
|
|
*
|
|
* Gets a
|
|
* [D-Bus address](https://dbus.freedesktop.org/doc/dbus-specification.html#addresses)
|
|
* string that can be used by clients to connect to @server.
|
|
*
|
|
* This is valid and non-empty if initializing the #GDBusServer succeeded.
|
|
*
|
|
* Returns: (not nullable): A D-Bus address string. Do not free, the string is owned
|
|
* by @server.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
const gchar *
|
|
g_dbus_server_get_client_address (GDBusServer *server)
|
|
{
|
|
g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL);
|
|
return server->client_address;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_server_get_guid:
|
|
* @server: A #GDBusServer.
|
|
*
|
|
* Gets the GUID for @server, as provided to g_dbus_server_new_sync().
|
|
*
|
|
* Returns: (not nullable): A D-Bus GUID. Do not free this string, it is owned by @server.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
const gchar *
|
|
g_dbus_server_get_guid (GDBusServer *server)
|
|
{
|
|
g_return_val_if_fail (G_IS_DBUS_SERVER (server), NULL);
|
|
return server->guid;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_server_get_flags:
|
|
* @server: A #GDBusServer.
|
|
*
|
|
* Gets the flags for @server.
|
|
*
|
|
* Returns: A set of flags from the #GDBusServerFlags enumeration.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
GDBusServerFlags
|
|
g_dbus_server_get_flags (GDBusServer *server)
|
|
{
|
|
g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE);
|
|
return server->flags;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_server_is_active:
|
|
* @server: A #GDBusServer.
|
|
*
|
|
* Gets whether @server is active.
|
|
*
|
|
* Returns: %TRUE if server is active, %FALSE otherwise.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
gboolean
|
|
g_dbus_server_is_active (GDBusServer *server)
|
|
{
|
|
g_return_val_if_fail (G_IS_DBUS_SERVER (server), G_DBUS_SERVER_FLAGS_NONE);
|
|
return server->active;
|
|
}
|
|
|
|
/**
|
|
* g_dbus_server_start:
|
|
* @server: A #GDBusServer.
|
|
*
|
|
* Starts @server.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
void
|
|
g_dbus_server_start (GDBusServer *server)
|
|
{
|
|
g_return_if_fail (G_IS_DBUS_SERVER (server));
|
|
if (server->active)
|
|
return;
|
|
/* Right now we don't have any transport not using the listener... */
|
|
g_assert (server->is_using_listener);
|
|
server->run_signal_handler_id = g_signal_connect_data (G_SOCKET_SERVICE (server->listener),
|
|
"run",
|
|
G_CALLBACK (on_run),
|
|
g_object_ref (server),
|
|
(GClosureNotify) g_object_unref,
|
|
G_CONNECT_DEFAULT);
|
|
g_socket_service_start (G_SOCKET_SERVICE (server->listener));
|
|
server->active = TRUE;
|
|
g_object_notify (G_OBJECT (server), "active");
|
|
}
|
|
|
|
/**
|
|
* g_dbus_server_stop:
|
|
* @server: A #GDBusServer.
|
|
*
|
|
* Stops @server.
|
|
*
|
|
* Since: 2.26
|
|
*/
|
|
void
|
|
g_dbus_server_stop (GDBusServer *server)
|
|
{
|
|
g_return_if_fail (G_IS_DBUS_SERVER (server));
|
|
if (!server->active)
|
|
return;
|
|
/* Right now we don't have any transport not using the listener... */
|
|
g_assert (server->is_using_listener);
|
|
g_assert (server->run_signal_handler_id > 0);
|
|
g_clear_signal_handler (&server->run_signal_handler_id, server->listener);
|
|
g_socket_service_stop (G_SOCKET_SERVICE (server->listener));
|
|
server->active = FALSE;
|
|
g_object_notify (G_OBJECT (server), "active");
|
|
|
|
if (server->unix_socket_path)
|
|
{
|
|
if (g_unlink (server->unix_socket_path) != 0)
|
|
g_warning ("Failed to delete %s: %s", server->unix_socket_path, g_strerror (errno));
|
|
}
|
|
|
|
if (server->nonce_file)
|
|
{
|
|
if (g_unlink (server->nonce_file) != 0)
|
|
g_warning ("Failed to delete %s: %s", server->nonce_file, g_strerror (errno));
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
static gint
|
|
random_ascii (void)
|
|
{
|
|
gint ret;
|
|
ret = g_random_int_range (0, 60);
|
|
if (ret < 25)
|
|
ret += 'A';
|
|
else if (ret < 50)
|
|
ret += 'a' - 25;
|
|
else
|
|
ret += '0' - 50;
|
|
return ret;
|
|
}
|
|
|
|
/* note that address_entry has already been validated => exactly one of path, dir, tmpdir, or abstract keys are set */
|
|
static gboolean
|
|
try_unix (GDBusServer *server,
|
|
const gchar *address_entry,
|
|
GHashTable *key_value_pairs,
|
|
GError **error)
|
|
{
|
|
gboolean ret;
|
|
const gchar *path;
|
|
const gchar *dir;
|
|
const gchar *tmpdir;
|
|
const gchar *abstract;
|
|
GSocketAddress *address;
|
|
|
|
ret = FALSE;
|
|
address = NULL;
|
|
|
|
path = g_hash_table_lookup (key_value_pairs, "path");
|
|
dir = g_hash_table_lookup (key_value_pairs, "dir");
|
|
tmpdir = g_hash_table_lookup (key_value_pairs, "tmpdir");
|
|
abstract = g_hash_table_lookup (key_value_pairs, "abstract");
|
|
|
|
if (path != NULL)
|
|
{
|
|
address = g_unix_socket_address_new (path);
|
|
}
|
|
else if (dir != NULL || tmpdir != NULL)
|
|
{
|
|
gint n;
|
|
GString *s;
|
|
GError *local_error;
|
|
|
|
retry:
|
|
s = g_string_new (tmpdir != NULL ? tmpdir : dir);
|
|
g_string_append (s, "/dbus-");
|
|
for (n = 0; n < 8; n++)
|
|
g_string_append_c (s, random_ascii ());
|
|
|
|
address = g_unix_socket_address_new (s->str);
|
|
g_string_free (s, TRUE);
|
|
|
|
local_error = NULL;
|
|
if (!g_socket_listener_add_address (server->listener,
|
|
address,
|
|
G_SOCKET_TYPE_STREAM,
|
|
G_SOCKET_PROTOCOL_DEFAULT,
|
|
NULL, /* source_object */
|
|
NULL, /* effective_address */
|
|
&local_error))
|
|
{
|
|
if (local_error->domain == G_IO_ERROR && local_error->code == G_IO_ERROR_ADDRESS_IN_USE)
|
|
{
|
|
g_error_free (local_error);
|
|
goto retry;
|
|
}
|
|
g_propagate_error (error, local_error);
|
|
goto out;
|
|
}
|
|
ret = TRUE;
|
|
goto out;
|
|
}
|
|
else if (abstract != NULL)
|
|
{
|
|
if (!g_unix_socket_address_abstract_names_supported ())
|
|
{
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_NOT_SUPPORTED,
|
|
_("Abstract namespace not supported"));
|
|
goto out;
|
|
}
|
|
address = g_unix_socket_address_new_with_type (abstract,
|
|
-1,
|
|
G_UNIX_SOCKET_ADDRESS_ABSTRACT);
|
|
}
|
|
else
|
|
{
|
|
g_assert_not_reached ();
|
|
}
|
|
|
|
if (!g_socket_listener_add_address (server->listener,
|
|
address,
|
|
G_SOCKET_TYPE_STREAM,
|
|
G_SOCKET_PROTOCOL_DEFAULT,
|
|
NULL, /* source_object */
|
|
NULL, /* effective_address */
|
|
error))
|
|
goto out;
|
|
|
|
ret = TRUE;
|
|
|
|
out:
|
|
|
|
if (address != NULL)
|
|
{
|
|
/* Fill out client_address if the connection attempt worked */
|
|
if (ret)
|
|
{
|
|
const char *address_path;
|
|
char *escaped_path;
|
|
|
|
server->is_using_listener = TRUE;
|
|
address_path = g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (address));
|
|
escaped_path = g_dbus_address_escape_value (address_path);
|
|
|
|
switch (g_unix_socket_address_get_address_type (G_UNIX_SOCKET_ADDRESS (address)))
|
|
{
|
|
case G_UNIX_SOCKET_ADDRESS_ABSTRACT:
|
|
server->client_address = g_strdup_printf ("unix:abstract=%s", escaped_path);
|
|
break;
|
|
|
|
case G_UNIX_SOCKET_ADDRESS_PATH:
|
|
server->client_address = g_strdup_printf ("unix:path=%s", escaped_path);
|
|
server->unix_socket_path = g_strdup (address_path);
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
g_free (escaped_path);
|
|
}
|
|
g_object_unref (address);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
/* note that address_entry has already been validated =>
|
|
* both host and port (guaranteed to be a number in [0, 65535]) are set (family is optional)
|
|
*/
|
|
static gboolean
|
|
try_tcp (GDBusServer *server,
|
|
const gchar *address_entry,
|
|
GHashTable *key_value_pairs,
|
|
gboolean do_nonce,
|
|
GError **error)
|
|
{
|
|
gboolean ret;
|
|
const gchar *host;
|
|
const gchar *port;
|
|
gint port_num;
|
|
GResolver *resolver;
|
|
GList *resolved_addresses;
|
|
GList *l;
|
|
|
|
ret = FALSE;
|
|
resolver = NULL;
|
|
resolved_addresses = NULL;
|
|
|
|
host = g_hash_table_lookup (key_value_pairs, "host");
|
|
port = g_hash_table_lookup (key_value_pairs, "port");
|
|
/* family = g_hash_table_lookup (key_value_pairs, "family"); */
|
|
if (g_hash_table_lookup (key_value_pairs, "noncefile") != NULL)
|
|
{
|
|
g_set_error_literal (error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
_("Cannot specify nonce file when creating a server"));
|
|
goto out;
|
|
}
|
|
|
|
if (host == NULL)
|
|
host = "localhost";
|
|
if (port == NULL)
|
|
port = "0";
|
|
port_num = strtol (port, NULL, 10);
|
|
|
|
resolver = g_resolver_get_default ();
|
|
resolved_addresses = g_resolver_lookup_by_name (resolver,
|
|
host,
|
|
NULL,
|
|
error);
|
|
if (resolved_addresses == NULL)
|
|
goto out;
|
|
|
|
/* TODO: handle family */
|
|
for (l = resolved_addresses; l != NULL; l = l->next)
|
|
{
|
|
GInetAddress *address = G_INET_ADDRESS (l->data);
|
|
GSocketAddress *socket_address;
|
|
GSocketAddress *effective_address;
|
|
|
|
socket_address = g_inet_socket_address_new (address, port_num);
|
|
if (!g_socket_listener_add_address (server->listener,
|
|
socket_address,
|
|
G_SOCKET_TYPE_STREAM,
|
|
G_SOCKET_PROTOCOL_TCP,
|
|
NULL, /* GObject *source_object */
|
|
&effective_address,
|
|
error))
|
|
{
|
|
g_object_unref (socket_address);
|
|
goto out;
|
|
}
|
|
if (port_num == 0)
|
|
/* make sure we allocate the same port number for other listeners */
|
|
port_num = g_inet_socket_address_get_port (G_INET_SOCKET_ADDRESS (effective_address));
|
|
|
|
g_object_unref (effective_address);
|
|
g_object_unref (socket_address);
|
|
}
|
|
|
|
if (do_nonce)
|
|
{
|
|
gint fd;
|
|
guint n;
|
|
gsize bytes_written;
|
|
gsize bytes_remaining;
|
|
char *file_escaped;
|
|
char *host_escaped;
|
|
|
|
server->nonce = g_new0 (guchar, 16);
|
|
for (n = 0; n < 16; n++)
|
|
server->nonce[n] = g_random_int_range (0, 256);
|
|
fd = g_file_open_tmp ("gdbus-nonce-file-XXXXXX",
|
|
&server->nonce_file,
|
|
error);
|
|
if (fd == -1)
|
|
{
|
|
g_socket_listener_close (server->listener);
|
|
goto out;
|
|
}
|
|
again:
|
|
bytes_written = 0;
|
|
bytes_remaining = 16;
|
|
while (bytes_remaining > 0)
|
|
{
|
|
gssize size;
|
|
int errsv;
|
|
|
|
size = write (fd, server->nonce + bytes_written, bytes_remaining);
|
|
errsv = errno;
|
|
if (size == -1)
|
|
{
|
|
if (errsv == EINTR)
|
|
goto again;
|
|
g_set_error (error,
|
|
G_IO_ERROR,
|
|
g_io_error_from_errno (errsv),
|
|
_("Error writing nonce file at “%s”: %s"),
|
|
server->nonce_file,
|
|
g_strerror (errsv));
|
|
goto out;
|
|
}
|
|
bytes_written += size;
|
|
bytes_remaining -= size;
|
|
}
|
|
if (!g_close (fd, error))
|
|
goto out;
|
|
host_escaped = g_dbus_address_escape_value (host);
|
|
file_escaped = g_dbus_address_escape_value (server->nonce_file);
|
|
server->client_address = g_strdup_printf ("nonce-tcp:host=%s,port=%d,noncefile=%s",
|
|
host_escaped,
|
|
port_num,
|
|
file_escaped);
|
|
g_free (host_escaped);
|
|
g_free (file_escaped);
|
|
}
|
|
else
|
|
{
|
|
server->client_address = g_strdup_printf ("tcp:host=%s,port=%d", host, port_num);
|
|
}
|
|
server->is_using_listener = TRUE;
|
|
ret = TRUE;
|
|
|
|
out:
|
|
g_list_free_full (resolved_addresses, g_object_unref);
|
|
if (resolver)
|
|
g_object_unref (resolver);
|
|
return ret;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|
|
|
|
typedef struct
|
|
{
|
|
GDBusServer *server;
|
|
GDBusConnection *connection;
|
|
} EmitIdleData;
|
|
|
|
static void
|
|
emit_idle_data_free (EmitIdleData *data)
|
|
{
|
|
g_object_unref (data->server);
|
|
g_object_unref (data->connection);
|
|
g_free (data);
|
|
}
|
|
|
|
static gboolean
|
|
emit_new_connection_in_idle (gpointer user_data)
|
|
{
|
|
EmitIdleData *data = user_data;
|
|
gboolean claimed;
|
|
|
|
claimed = FALSE;
|
|
g_signal_emit (data->server,
|
|
_signals[NEW_CONNECTION_SIGNAL],
|
|
0,
|
|
data->connection,
|
|
&claimed);
|
|
|
|
if (claimed)
|
|
g_dbus_connection_start_message_processing (data->connection);
|
|
g_object_unref (data->connection);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Called in new thread */
|
|
static gboolean
|
|
on_run (GSocketService *service,
|
|
GSocketConnection *socket_connection,
|
|
GObject *source_object,
|
|
gpointer user_data)
|
|
{
|
|
GDBusServer *server = G_DBUS_SERVER (user_data);
|
|
GDBusConnection *connection;
|
|
GDBusConnectionFlags connection_flags;
|
|
|
|
if (server->nonce != NULL)
|
|
{
|
|
gchar buf[16];
|
|
gsize bytes_read;
|
|
|
|
if (!g_input_stream_read_all (g_io_stream_get_input_stream (G_IO_STREAM (socket_connection)),
|
|
buf,
|
|
16,
|
|
&bytes_read,
|
|
NULL, /* GCancellable */
|
|
NULL)) /* GError */
|
|
goto out;
|
|
|
|
if (bytes_read != 16)
|
|
goto out;
|
|
|
|
if (memcmp (buf, server->nonce, 16) != 0)
|
|
goto out;
|
|
}
|
|
|
|
connection_flags =
|
|
G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER |
|
|
G_DBUS_CONNECTION_FLAGS_DELAY_MESSAGE_PROCESSING;
|
|
if (server->flags & G_DBUS_SERVER_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS)
|
|
connection_flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_ALLOW_ANONYMOUS;
|
|
if (server->flags & G_DBUS_SERVER_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER)
|
|
connection_flags |= G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_REQUIRE_SAME_USER;
|
|
|
|
connection = g_dbus_connection_new_sync (G_IO_STREAM (socket_connection),
|
|
server->guid,
|
|
connection_flags,
|
|
server->authentication_observer,
|
|
NULL, /* GCancellable */
|
|
NULL); /* GError */
|
|
if (connection == NULL)
|
|
goto out;
|
|
|
|
if (server->flags & G_DBUS_SERVER_FLAGS_RUN_IN_THREAD)
|
|
{
|
|
gboolean claimed;
|
|
|
|
claimed = FALSE;
|
|
g_signal_emit (server,
|
|
_signals[NEW_CONNECTION_SIGNAL],
|
|
0,
|
|
connection,
|
|
&claimed);
|
|
if (claimed)
|
|
g_dbus_connection_start_message_processing (connection);
|
|
g_object_unref (connection);
|
|
}
|
|
else
|
|
{
|
|
GSource *idle_source;
|
|
EmitIdleData *data;
|
|
|
|
data = g_new0 (EmitIdleData, 1);
|
|
data->server = g_object_ref (server);
|
|
data->connection = g_object_ref (connection);
|
|
|
|
idle_source = g_idle_source_new ();
|
|
g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
|
|
g_source_set_callback (idle_source,
|
|
emit_new_connection_in_idle,
|
|
data,
|
|
(GDestroyNotify) emit_idle_data_free);
|
|
g_source_set_static_name (idle_source, "[gio] emit_new_connection_in_idle");
|
|
g_source_attach (idle_source, server->main_context_at_construction);
|
|
g_source_unref (idle_source);
|
|
}
|
|
|
|
out:
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
initable_init (GInitable *initable,
|
|
GCancellable *cancellable,
|
|
GError **error)
|
|
{
|
|
GDBusServer *server = G_DBUS_SERVER (initable);
|
|
gboolean ret;
|
|
guint n;
|
|
gchar **addr_array;
|
|
GError *last_error;
|
|
|
|
ret = FALSE;
|
|
addr_array = NULL;
|
|
last_error = NULL;
|
|
|
|
if (!g_dbus_is_guid (server->guid))
|
|
{
|
|
g_set_error (&last_error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
_("The string “%s” is not a valid D-Bus GUID"),
|
|
server->guid);
|
|
goto out;
|
|
}
|
|
|
|
server->listener = G_SOCKET_LISTENER (g_threaded_socket_service_new (-1));
|
|
|
|
addr_array = g_strsplit (server->address, ";", 0);
|
|
last_error = NULL;
|
|
for (n = 0; addr_array != NULL && addr_array[n] != NULL; n++)
|
|
{
|
|
const gchar *address_entry = addr_array[n];
|
|
GHashTable *key_value_pairs;
|
|
gchar *transport_name;
|
|
GError *this_error;
|
|
|
|
this_error = NULL;
|
|
if (g_dbus_is_supported_address (address_entry,
|
|
&this_error) &&
|
|
_g_dbus_address_parse_entry (address_entry,
|
|
&transport_name,
|
|
&key_value_pairs,
|
|
&this_error))
|
|
{
|
|
|
|
if (FALSE)
|
|
{
|
|
}
|
|
else if (g_strcmp0 (transport_name, "unix") == 0)
|
|
ret = try_unix (server, address_entry, key_value_pairs, &this_error);
|
|
else if (g_strcmp0 (transport_name, "tcp") == 0)
|
|
ret = try_tcp (server, address_entry, key_value_pairs, FALSE, &this_error);
|
|
else if (g_strcmp0 (transport_name, "nonce-tcp") == 0)
|
|
ret = try_tcp (server, address_entry, key_value_pairs, TRUE, &this_error);
|
|
else
|
|
g_set_error (&this_error,
|
|
G_IO_ERROR,
|
|
G_IO_ERROR_INVALID_ARGUMENT,
|
|
_("Cannot listen on unsupported transport “%s”"),
|
|
transport_name);
|
|
|
|
g_free (transport_name);
|
|
if (key_value_pairs != NULL)
|
|
g_hash_table_unref (key_value_pairs);
|
|
|
|
if (ret)
|
|
{
|
|
g_assert (this_error == NULL);
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (this_error != NULL)
|
|
{
|
|
if (last_error != NULL)
|
|
g_error_free (last_error);
|
|
last_error = this_error;
|
|
}
|
|
}
|
|
|
|
out:
|
|
|
|
g_strfreev (addr_array);
|
|
|
|
if (ret)
|
|
{
|
|
g_clear_error (&last_error);
|
|
}
|
|
else
|
|
{
|
|
g_assert (last_error != NULL);
|
|
g_propagate_error (error, last_error);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
static void
|
|
initable_iface_init (GInitableIface *initable_iface)
|
|
{
|
|
initable_iface->init = initable_init;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------------------------------------- */
|