/* vim: set et ts=8 sw=8: */ /* gclue-client-info.c * * Copyright 2013 Red Hat, Inc. * * Geoclue is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * Geoclue 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License along * with Geoclue; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * Author: Zeeshan Ali (Khattak) */ #include #include "gclue-client-info.h" #define MAX_CMDLINE_LEN 4096 static void gclue_client_info_async_initable_init (GAsyncInitableIface *iface); struct _GClueClientInfoPrivate { char *bus_name; GDBusConnection *connection; GDBusProxy *dbus_proxy; guint watch_id; guint32 user_id; char *xdg_id; }; G_DEFINE_TYPE_WITH_CODE (GClueClientInfo, gclue_client_info, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, gclue_client_info_async_initable_init) G_ADD_PRIVATE (GClueClientInfo)); enum { PROP_0, PROP_PEER, PROP_CONNECTION, LAST_PROP }; static GParamSpec *gParamSpecs[LAST_PROP]; enum { PEER_VANISHED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST]; static void gclue_client_info_finalize (GObject *object) { GClueClientInfoPrivate *priv = GCLUE_CLIENT_INFO (object)->priv; if (priv->watch_id != 0) { g_bus_unwatch_name (priv->watch_id); priv->watch_id = 0; } g_clear_object (&priv->dbus_proxy); g_clear_pointer (&priv->bus_name, g_free); g_clear_pointer (&priv->xdg_id, g_free); g_clear_object (&priv->connection); /* Chain up to the parent class */ G_OBJECT_CLASS (gclue_client_info_parent_class)->finalize (object); } static void gclue_client_info_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GClueClientInfo *info = GCLUE_CLIENT_INFO (object); switch (prop_id) { case PROP_PEER: g_value_set_string (value, info->priv->bus_name); break; case PROP_CONNECTION: g_value_set_object (value, info->priv->connection); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gclue_client_info_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GClueClientInfo *info = GCLUE_CLIENT_INFO (object); switch (prop_id) { case PROP_PEER: info->priv->bus_name = g_value_dup_string (value); break; case PROP_CONNECTION: info->priv->connection = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void gclue_client_info_class_init (GClueClientInfoClass *klass) { GObjectClass *object_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = gclue_client_info_finalize; object_class->get_property = gclue_client_info_get_property; object_class->set_property = gclue_client_info_set_property; gParamSpecs[PROP_PEER] = g_param_spec_string ("bus-name", "BusName", "Bus name of client", NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_PEER, gParamSpecs[PROP_PEER]); gParamSpecs[PROP_CONNECTION] = g_param_spec_object ("connection", "Connection", "DBus Connection", G_TYPE_DBUS_CONNECTION, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); g_object_class_install_property (object_class, PROP_CONNECTION, gParamSpecs[PROP_CONNECTION]); signals[PEER_VANISHED] = g_signal_new ("peer-vanished", GCLUE_TYPE_CLIENT_INFO, G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GClueClientInfoClass, peer_vanished), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, G_TYPE_NONE); } static void on_name_vanished (GDBusConnection *connection, const gchar *name, gpointer user_data) { g_signal_emit (GCLUE_CLIENT_INFO (user_data), signals[PEER_VANISHED], 0); } static gchar * parse_cgroup_v2 (GStrv lines) { const char *unit, *name; char *dash, *xdg_id; g_autofree char *scope = NULL; /* Cgroup v2 is always a single line: * 0::/user.slice/user-1000.slice/user@1000.service/app.slice/app-flatpak-org.gnome.Maps-3358.scope */ if (g_strv_length (lines) != 2) return NULL; if (!g_str_has_prefix (lines[0], "0::")) return NULL; unit = lines[0] + strlen ("0::"); scope = g_path_get_basename (unit); if (!g_str_has_prefix (scope, "app-flatpak-") || !g_str_has_suffix (scope, ".scope")) return NULL; name = scope + strlen("app-flatpak-"); dash = strchr (name, '-'); if (dash == NULL) return NULL; *dash = 0; xdg_id = g_strdup (name); g_debug ("Found xdg_id %s", xdg_id); return xdg_id; } /* Based on got_credentials_cb() from xdg-app source code */ static char * get_xdg_id (guint32 pid) { char *xdg_id = NULL; g_autofree char *path = NULL; g_autofree char *content = NULL; g_auto(GStrv) lines = NULL; int i; path = g_strdup_printf ("/proc/%u/cgroup", pid); if (!g_file_get_contents (path, &content, NULL, NULL)) return NULL; lines = g_strsplit (content, "\n", -1); xdg_id = parse_cgroup_v2 (lines); if (xdg_id != NULL) return xdg_id; for (i = 0; lines[i] != NULL; i++) { const char *unit = lines[i] + strlen ("1:name=systemd:"); g_autofree char *scope = NULL; const char *name; char *dash; const char *prefix = NULL; if (!g_str_has_prefix (lines[i], "1:name=systemd:")) continue; scope = g_path_get_basename (unit); if (g_str_has_prefix (scope, "xdg-app-")) prefix = "xdg-app-"; else if (g_str_has_prefix (scope, "flatpak-")) prefix = "flatpak-"; else if (g_str_has_prefix (scope, "app-flatpak-")) prefix = "app-flatpak-"; if (prefix == NULL || !g_str_has_suffix (scope, ".scope")) break; name = scope + strlen(prefix); dash = strchr (name, '-'); if (dash == NULL) break; *dash = 0; xdg_id = g_strdup (name); } return xdg_id; } static void on_get_pid_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { GTask *task = G_TASK (user_data); gpointer *info = g_task_get_source_object (task); GClueClientInfoPrivate *priv = GCLUE_CLIENT_INFO (info)->priv; guint32 pid; GError *error = NULL; GVariant *results = NULL; results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); if (results == NULL) { g_task_return_error (task, error); g_object_unref (task); return; } g_assert (g_variant_n_children (results) > 0); g_variant_get_child (results, 0, "u", &pid); g_variant_unref (results); priv->xdg_id = get_xdg_id (pid); priv->watch_id = g_bus_watch_name_on_connection (priv->connection, priv->bus_name, G_BUS_NAME_WATCHER_FLAGS_NONE, NULL, on_name_vanished, info, NULL); g_task_return_boolean (task, TRUE); g_object_unref (task); } static void on_get_user_id_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { GTask *task = G_TASK (user_data); gpointer *info = g_task_get_source_object (task); GClueClientInfoPrivate *priv = GCLUE_CLIENT_INFO (info)->priv; GError *error = NULL; GVariant *results = NULL; results = g_dbus_proxy_call_finish (G_DBUS_PROXY (source_object), res, &error); if (results == NULL) { g_task_return_error (task, error); g_object_unref (task); return; } g_assert (g_variant_n_children (results) > 0); g_variant_get_child (results, 0, "u", &priv->user_id); g_variant_unref (results); g_dbus_proxy_call (priv->dbus_proxy, "GetConnectionUnixProcessID", g_variant_new ("(s)", priv->bus_name), G_DBUS_CALL_FLAGS_NONE, -1, g_task_get_cancellable (task), on_get_pid_ready, task); } static void on_dbus_proxy_ready (GObject *source_object, GAsyncResult *res, gpointer user_data) { GTask *task = G_TASK (user_data); gpointer *info = g_task_get_source_object (task); GClueClientInfoPrivate *priv = GCLUE_CLIENT_INFO (info)->priv; GDBusProxy *dbus_proxy; GError *error = NULL; dbus_proxy = g_dbus_proxy_new_for_bus_finish (res, &error); if (dbus_proxy == NULL) { g_task_return_error (task, error); g_object_unref (task); return; } priv->dbus_proxy = dbus_proxy; g_dbus_proxy_call (priv->dbus_proxy, "GetConnectionUnixUser", g_variant_new ("(s)", priv->bus_name), G_DBUS_CALL_FLAGS_NONE, -1, g_task_get_cancellable (task), on_get_user_id_ready, task); } static void gclue_client_info_init_async (GAsyncInitable *initable, int io_priority, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; task = g_task_new (initable, cancellable, callback, user_data); g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", cancellable, on_dbus_proxy_ready, task); } static gboolean gclue_client_info_init_finish (GAsyncInitable *initable, GAsyncResult *result, GError **error) { return g_task_propagate_boolean (G_TASK (result), error); } static void gclue_client_info_async_initable_init (GAsyncInitableIface *iface) { iface->init_async = gclue_client_info_init_async; iface->init_finish = gclue_client_info_init_finish; } static void gclue_client_info_init (GClueClientInfo *info) { info->priv = gclue_client_info_get_instance_private(info); } void gclue_client_info_new_async (const char *bus_name, GDBusConnection *connection, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { g_async_initable_new_async (GCLUE_TYPE_CLIENT_INFO, G_PRIORITY_DEFAULT, cancellable, callback, user_data, "bus-name", bus_name, "connection", connection, NULL); } GClueClientInfo * gclue_client_info_new_finish (GAsyncResult *res, GError **error) { GObject *object; GObject *source_object; source_object = g_async_result_get_source_object (res); object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object), res, error); g_object_unref (source_object); if (object != NULL) return GCLUE_CLIENT_INFO (object); else return NULL; } const gchar * gclue_client_info_get_bus_name (GClueClientInfo *info) { g_return_val_if_fail (GCLUE_IS_CLIENT_INFO(info), NULL); return info->priv->bus_name; } guint32 gclue_client_info_get_user_id (GClueClientInfo *info) { g_return_val_if_fail (GCLUE_IS_CLIENT_INFO(info), 0); return info->priv->user_id; } gboolean gclue_client_info_check_bus_name (GClueClientInfo *info, const char *bus_name) { g_return_val_if_fail (GCLUE_IS_CLIENT_INFO(info), FALSE); return (strcmp (bus_name, info->priv->bus_name) == 0); } const char * gclue_client_info_get_xdg_id (GClueClientInfo *info) { g_return_val_if_fail (GCLUE_IS_CLIENT_INFO(info), FALSE); return info->priv->xdg_id; }