Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2018 Endless Mobile, Inc.
4 : : *
5 : : * This library is free software; you can redistribute it and/or
6 : : * modify it under the terms of the GNU Lesser General Public
7 : : * License as published by the Free Software Foundation; either
8 : : * version 2.1 of the License, or (at your option) any later version.
9 : : *
10 : : * This library is distributed in the hope that it will be useful,
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 : : * Lesser General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU Lesser General Public
16 : : * License along with this library; if not, write to the Free Software
17 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 : : *
19 : : * Authors:
20 : : * - Philip Withnall <withnall@endlessm.com>
21 : : */
22 : :
23 : : #include "config.h"
24 : :
25 : : #include <glib.h>
26 : : #include <glib-object.h>
27 : : #include <glib/gi18n-lib.h>
28 : : #include <gio/gio.h>
29 : : #include <libmogwai-schedule/peer-manager.h>
30 : : #include <libmogwai-schedule/peer-manager-dbus.h>
31 : : #include <libmogwai-schedule/scheduler.h>
32 : : #include <stdlib.h>
33 : :
34 : :
35 : : static void mws_peer_manager_dbus_peer_manager_init (MwsPeerManagerInterface *iface);
36 : : static void mws_peer_manager_dbus_dispose (GObject *object);
37 : :
38 : : static void mws_peer_manager_dbus_get_property (GObject *object,
39 : : guint property_id,
40 : : GValue *value,
41 : : GParamSpec *pspec);
42 : : static void mws_peer_manager_dbus_set_property (GObject *object,
43 : : guint property_id,
44 : : const GValue *value,
45 : : GParamSpec *pspec);
46 : :
47 : : static void mws_peer_manager_dbus_ensure_peer_credentials_async (MwsPeerManager *manager,
48 : : const gchar *sender,
49 : : GCancellable *cancellable,
50 : : GAsyncReadyCallback callback,
51 : : gpointer user_data);
52 : : static gchar *mws_peer_manager_dbus_ensure_peer_credentials_finish (MwsPeerManager *manager,
53 : : GAsyncResult *result,
54 : : GError **error);
55 : : static const gchar *mws_peer_manager_dbus_get_peer_credentials (MwsPeerManager *manager,
56 : : const gchar *sender);
57 : :
58 : : /**
59 : : * MwsPeerManagerDBus:
60 : : *
61 : : * An implementation of the #MwsPeerManager interface which draws its data
62 : : * from the D-Bus daemon. This is the only expected runtime implementation of
63 : : * the interface, and has only been split out from the interface to allow for
64 : : * easier unit testing of anything which uses it.
65 : : *
66 : : * The credentials of a peer are retrieved from the D-Bus daemon using
67 : : * [`GetConnectionCredentials`](https://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-get-connection-credentials),
68 : : * and reading `/proc/$pid/cmdline` to get the absolute path to the executable
69 : : * for each peer, which we use as an identifier for it. This is not atomic or
70 : : * particularly trusted, as PIDs can be reused in the time it takes us to query
71 : : * the information, and processes can modify their own cmdline file, but
72 : : * without an LSM enabled in the kernel and dbus-daemon, it’s the best we can do
73 : : * for identifying processes.
74 : : *
75 : : * Since: 0.1.0
76 : : */
77 : : struct _MwsPeerManagerDBus
78 : : {
79 : : GObject parent;
80 : :
81 : : GDBusConnection *connection; /* (owned) */
82 : :
83 : : /* Hold the watch IDs of all peers who have added entries at some point. */
84 : : GPtrArray *peer_watch_ids; /* (owned) */
85 : :
86 : : /* Cache of peer credentials (currently only the executable path of each peer). */
87 : : GHashTable *peer_credentials; /* (owned) (element-type utf8 filename) */
88 : : };
89 : :
90 : : typedef enum
91 : : {
92 : : PROP_CONNECTION = 1,
93 : : } MwsPeerManagerDBusProperty;
94 : :
95 [ # # # # : 0 : G_DEFINE_TYPE_WITH_CODE (MwsPeerManagerDBus, mws_peer_manager_dbus, G_TYPE_OBJECT,
# # ]
96 : : G_IMPLEMENT_INTERFACE (MWS_TYPE_PEER_MANAGER,
97 : : mws_peer_manager_dbus_peer_manager_init))
98 : : static void
99 : 0 : mws_peer_manager_dbus_class_init (MwsPeerManagerDBusClass *klass)
100 : : {
101 : 0 : GObjectClass *object_class = (GObjectClass *) klass;
102 : 0 : GParamSpec *props[PROP_CONNECTION + 1] = { NULL, };
103 : :
104 : 0 : object_class->dispose = mws_peer_manager_dbus_dispose;
105 : 0 : object_class->get_property = mws_peer_manager_dbus_get_property;
106 : 0 : object_class->set_property = mws_peer_manager_dbus_set_property;
107 : :
108 : : /**
109 : : * MwsPeerManagerDBus:connection:
110 : : *
111 : : * D-Bus connection to use for retrieving peer credentials.
112 : : *
113 : : * Since: 0.1.0
114 : : */
115 : 0 : props[PROP_CONNECTION] =
116 : 0 : g_param_spec_object ("connection", "Connection",
117 : : "D-Bus connection to use for retrieving peer credentials.",
118 : : G_TYPE_DBUS_CONNECTION,
119 : : G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
120 : :
121 : 0 : g_object_class_install_properties (object_class, G_N_ELEMENTS (props), props);
122 : 0 : }
123 : :
124 : : static void
125 : 0 : mws_peer_manager_dbus_peer_manager_init (MwsPeerManagerInterface *iface)
126 : : {
127 : 0 : iface->ensure_peer_credentials_async = mws_peer_manager_dbus_ensure_peer_credentials_async;
128 : 0 : iface->ensure_peer_credentials_finish = mws_peer_manager_dbus_ensure_peer_credentials_finish;
129 : 0 : iface->get_peer_credentials = mws_peer_manager_dbus_get_peer_credentials;
130 : 0 : }
131 : :
132 : : static void
133 : 0 : watcher_id_free (gpointer data)
134 : : {
135 : 0 : g_bus_unwatch_name (GPOINTER_TO_UINT (data));
136 : 0 : }
137 : :
138 : : static void
139 : 0 : mws_peer_manager_dbus_init (MwsPeerManagerDBus *self)
140 : : {
141 : 0 : self->peer_watch_ids = g_ptr_array_new_with_free_func (watcher_id_free);
142 : 0 : self->peer_credentials = g_hash_table_new_full (g_str_hash, g_str_equal,
143 : : g_free, g_free);
144 : 0 : }
145 : :
146 : : static void
147 : 0 : mws_peer_manager_dbus_dispose (GObject *object)
148 : : {
149 : 0 : MwsPeerManagerDBus *self = MWS_PEER_MANAGER_DBUS (object);
150 : :
151 [ # # ]: 0 : g_clear_object (&self->connection);
152 : :
153 [ # # ]: 0 : g_clear_pointer (&self->peer_credentials, g_hash_table_unref);
154 [ # # ]: 0 : g_clear_pointer (&self->peer_watch_ids, g_ptr_array_unref);
155 : :
156 : : /* Chain up to the parent class */
157 : 0 : G_OBJECT_CLASS (mws_peer_manager_dbus_parent_class)->dispose (object);
158 : 0 : }
159 : :
160 : : static void
161 : 0 : mws_peer_manager_dbus_get_property (GObject *object,
162 : : guint property_id,
163 : : GValue *value,
164 : : GParamSpec *pspec)
165 : : {
166 : 0 : MwsPeerManagerDBus *self = MWS_PEER_MANAGER_DBUS (object);
167 : :
168 [ # # ]: 0 : switch ((MwsPeerManagerDBusProperty) property_id)
169 : : {
170 : 0 : case PROP_CONNECTION:
171 : 0 : g_value_set_object (value, self->connection);
172 : 0 : break;
173 : 0 : default:
174 : 0 : g_assert_not_reached ();
175 : : }
176 : 0 : }
177 : :
178 : : static void
179 : 0 : mws_peer_manager_dbus_set_property (GObject *object,
180 : : guint property_id,
181 : : const GValue *value,
182 : : GParamSpec *pspec)
183 : : {
184 : 0 : MwsPeerManagerDBus *self = MWS_PEER_MANAGER_DBUS (object);
185 : :
186 [ # # ]: 0 : switch ((MwsPeerManagerDBusProperty) property_id)
187 : : {
188 : 0 : case PROP_CONNECTION:
189 : : /* Construct only. */
190 [ # # ]: 0 : g_assert (self->connection == NULL);
191 : 0 : self->connection = g_value_dup_object (value);
192 : 0 : break;
193 : 0 : default:
194 : 0 : g_assert_not_reached ();
195 : : }
196 : 0 : }
197 : :
198 : : static void
199 : 0 : peer_vanished_cb (GDBusConnection *connection,
200 : : const gchar *name,
201 : : gpointer user_data)
202 : : {
203 : 0 : MwsPeerManagerDBus *self = MWS_PEER_MANAGER_DBUS (user_data);
204 : :
205 : 0 : g_debug ("%s: Removing peer credentials for ‘%s’ from cache", G_STRFUNC, name);
206 [ # # ]: 0 : if (g_hash_table_remove (self->peer_credentials, name))
207 : : {
208 : : /* Notify users of this API. */
209 : 0 : g_signal_emit_by_name (self, "peer-vanished", name);
210 : : }
211 : 0 : }
212 : :
213 : : /* An async function for getting credentials for D-Bus peers, either by querying
214 : : * the bus, or by getting them from a cache. */
215 : : static void ensure_peer_credentials_cb (GObject *obj,
216 : : GAsyncResult *result,
217 : : gpointer user_data);
218 : :
219 : : static void
220 : 0 : mws_peer_manager_dbus_ensure_peer_credentials_async (MwsPeerManager *manager,
221 : : const gchar *sender,
222 : : GCancellable *cancellable,
223 : : GAsyncReadyCallback callback,
224 : : gpointer user_data)
225 : : {
226 : 0 : MwsPeerManagerDBus *self = MWS_PEER_MANAGER_DBUS (manager);
227 : :
228 : 0 : g_autoptr(GTask) task = g_task_new (self, cancellable, callback, user_data);
229 [ # # ]: 0 : g_task_set_source_tag (task, mws_peer_manager_dbus_ensure_peer_credentials_async);
230 : 0 : g_task_set_task_data (task, g_strdup (sender), g_free);
231 : :
232 : : /* Look up information about the sender so that we can (for example)
233 : : * prioritise downloads by sender. */
234 : 0 : const gchar *peer_path = mws_peer_manager_get_peer_credentials (manager, sender);
235 : :
236 [ # # ]: 0 : if (peer_path != NULL)
237 : : {
238 : 0 : g_debug ("%s: Found credentials in cache; path is ‘%s’",
239 : : G_STRFUNC, peer_path);
240 : 0 : g_task_return_pointer (task, g_strdup (peer_path), g_free);
241 : : }
242 : : else
243 : : {
244 : : /* Watch the peer so we can know if/when it disappears. */
245 : 0 : guint watch_id = g_bus_watch_name_on_connection (self->connection, sender,
246 : : G_BUS_NAME_WATCHER_FLAGS_NONE,
247 : : NULL, peer_vanished_cb,
248 : : self, NULL);
249 : 0 : g_ptr_array_add (self->peer_watch_ids, GUINT_TO_POINTER (watch_id));
250 : :
251 : : /* And query for its credentials. */
252 : 0 : g_dbus_connection_call (self->connection, "org.freedesktop.DBus", "/",
253 : : "org.freedesktop.DBus", "GetConnectionCredentials",
254 : : g_variant_new ("(s)", sender),
255 : : G_VARIANT_TYPE ("(a{sv})"),
256 : : G_DBUS_CALL_FLAGS_NONE,
257 : : -1 /* default timeout */,
258 : : cancellable,
259 : : ensure_peer_credentials_cb, g_steal_pointer (&task));
260 : : }
261 : 0 : }
262 : :
263 : : static void
264 : 0 : ensure_peer_credentials_cb (GObject *obj,
265 : : GAsyncResult *result,
266 : : gpointer user_data)
267 : : {
268 [ # # ]: 0 : g_autoptr(GTask) task = G_TASK (user_data);
269 : 0 : MwsPeerManagerDBus *self = MWS_PEER_MANAGER_DBUS (g_task_get_source_object (task));
270 : 0 : GDBusConnection *connection = G_DBUS_CONNECTION (obj);
271 : 0 : const gchar *sender = g_task_get_task_data (task);
272 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
273 : :
274 : : /* Finish looking up the sender. */
275 [ # # ]: 0 : g_autoptr(GVariant) retval = NULL;
276 : 0 : retval = g_dbus_connection_call_finish (connection, result, &local_error);
277 : :
278 [ # # ]: 0 : if (retval == NULL)
279 : : {
280 : 0 : g_task_return_error (task, g_steal_pointer (&local_error));
281 : 0 : return;
282 : : }
283 : :
284 : : /* From the credentials information from D-Bus, we can get the process ID,
285 : : * and then look up the process name. Note that this is racy (the process
286 : : * ID may get recycled between GetConnectionCredentials() returning and us
287 : : * querying the kernel for the process name), but there’s nothing we can
288 : : * do about that. The correct approach is to use an LSM label as returned
289 : : * by GetConnectionCredentials(), but EOS doesn’t support any LSM.
290 : : * We would look at /proc/$pid/exe, but that requires elevated privileges
291 : : * (CAP_SYS_PTRACE, but I can’t get that working; so it would require root
292 : : * privileges). Instead, we look at /proc/$pid/cmdline, which is accessible
293 : : * by all. Unfortunately, it is also forgeable. Thankfully this only affects
294 : : * the priority of download scheduling, not anything security critical. */
295 : : guint process_id;
296 : :
297 [ # # ]: 0 : g_autoptr(GVariant) credentials = g_variant_get_child_value (retval, 0);
298 : :
299 [ # # ]: 0 : if (!g_variant_lookup (credentials, "ProcessID", "u", &process_id))
300 : : {
301 : 0 : g_task_return_new_error (task, MWS_SCHEDULER_ERROR,
302 : : MWS_SCHEDULER_ERROR_IDENTIFYING_PEER,
303 : 0 : _("Process ID for peer ‘%s’ could not be determined"),
304 : : sender);
305 : 0 : return;
306 : : }
307 : :
308 [ # # ]: 0 : g_autofree gchar *pid_str = g_strdup_printf ("%u", process_id);
309 [ # # ]: 0 : g_autofree gchar *proc_pid_cmdline = g_build_filename ("/proc", pid_str, "cmdline", NULL);
310 [ # # ]: 0 : g_autofree gchar *cmdline = NULL;
311 : :
312 : 0 : g_debug ("%s: Getting contents of ‘%s’", G_STRFUNC, proc_pid_cmdline);
313 : :
314 : : /* Assume the path is always the first nul-terminated segment. */
315 [ # # ]: 0 : if (!g_file_get_contents (proc_pid_cmdline, &cmdline, NULL, &local_error))
316 : : {
317 : 0 : g_task_return_new_error (task, MWS_SCHEDULER_ERROR,
318 : : MWS_SCHEDULER_ERROR_IDENTIFYING_PEER,
319 : 0 : _("Executable path for peer ‘%s’ (process ID: %s) "
320 : : "could not be determined: %s"),
321 : 0 : sender, pid_str, local_error->message);
322 : 0 : return;
323 : : }
324 : :
325 : : /* Resolve to an absolute path, since what we get back might not be absolute. */
326 [ # # ]: 0 : g_autofree gchar *sender_path = g_find_program_in_path (cmdline);
327 : :
328 [ # # ]: 0 : if (sender_path == NULL)
329 : : {
330 : 0 : g_autofree gchar *message =
331 : 0 : g_strdup_printf (_("Path ‘%s’ could not be resolved"), cmdline);
332 : 0 : g_task_return_new_error (task, MWS_SCHEDULER_ERROR,
333 : : MWS_SCHEDULER_ERROR_IDENTIFYING_PEER,
334 : 0 : _("Executable path for peer ‘%s’ (process ID: %s) "
335 : : "could not be determined: %s"),
336 : : sender, pid_str, message);
337 : 0 : return;
338 : : }
339 : :
340 : 0 : g_debug ("%s: Got credentials from D-Bus daemon; path is ‘%s’ (resolved from ‘%s’)",
341 : : G_STRFUNC, sender_path, cmdline);
342 : :
343 : 0 : g_hash_table_replace (self->peer_credentials,
344 : 0 : g_strdup (sender), g_strdup (sender_path));
345 : :
346 : 0 : g_task_return_pointer (task, g_steal_pointer (&sender_path), g_free);
347 : : }
348 : :
349 : : static gchar *
350 : 0 : mws_peer_manager_dbus_ensure_peer_credentials_finish (MwsPeerManager *manager,
351 : : GAsyncResult *result,
352 : : GError **error)
353 : : {
354 [ # # ]: 0 : g_return_val_if_fail (g_task_is_valid (result, manager), NULL);
355 [ # # ]: 0 : g_return_val_if_fail (g_async_result_is_tagged (result, mws_peer_manager_dbus_ensure_peer_credentials_async), NULL);
356 : :
357 : 0 : return g_task_propagate_pointer (G_TASK (result), error);
358 : : }
359 : :
360 : : static const gchar *
361 : 0 : mws_peer_manager_dbus_get_peer_credentials (MwsPeerManager *manager,
362 : : const gchar *sender)
363 : : {
364 : 0 : MwsPeerManagerDBus *self = MWS_PEER_MANAGER_DBUS (manager);
365 : :
366 : 0 : g_debug ("%s: Querying credentials for peer ‘%s’", G_STRFUNC, sender);
367 : 0 : return g_hash_table_lookup (self->peer_credentials, sender);
368 : : }
369 : :
370 : : /**
371 : : * mws_peer_manager_dbus_new:
372 : : * @connection: a #GDBusConnection
373 : : *
374 : : * Create a #MwsPeerManagerDBus object to wrap the given existing @connection.
375 : : *
376 : : * Returns: (transfer full): a new #MwsPeerManagerDBus wrapping @connection
377 : : * Since: 0.1.0
378 : : */
379 : : MwsPeerManagerDBus *
380 : 0 : mws_peer_manager_dbus_new (GDBusConnection *connection)
381 : : {
382 [ # # # # : 0 : g_return_val_if_fail (G_IS_DBUS_CONNECTION (connection), NULL);
# # # # ]
383 : :
384 : 0 : return g_object_new (MWS_TYPE_PEER_MANAGER_DBUS,
385 : : "connection", connection,
386 : : NULL);
387 : : }
|