Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright 2025 GNOME Foundation, Inc.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General Public
18 : : * License along with this library; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 : : *
21 : : * Authors:
22 : : * - Philip Withnall <pwithnall@gnome.org>
23 : : */
24 : :
25 : : #include "config.h"
26 : :
27 : : #include <glib.h>
28 : : #include <glib-object.h>
29 : : #include <glib-unix.h>
30 : : #include <glib/gi18n-lib.h>
31 : : #include <gio/gio.h>
32 : : #include <locale.h>
33 : : #include <libgsystemservice/peer-manager.h>
34 : : #include <libgsystemservice/peer-manager-dbus.h>
35 : : #include <libgsystemservice/service.h>
36 : : #include <libmalcontent-web/filtering-dbus-service.h>
37 : : #include <libmalcontent-web/web-service.h>
38 : :
39 : :
40 : : static void mct_web_service_dispose (GObject *object);
41 : : static void mct_web_service_finalize (GObject *object);
42 : :
43 : : static void mct_web_service_startup_async (GssService *service,
44 : : GCancellable *cancellable,
45 : : GAsyncReadyCallback callback,
46 : : gpointer user_data);
47 : : static void mct_web_service_startup_finish (GssService *service,
48 : : GAsyncResult *result,
49 : : GError **error);
50 : : static void mct_web_service_shutdown (GssService *service);
51 : :
52 : : static void notify_busy_cb (GObject *obj,
53 : : GParamSpec *pspec,
54 : : gpointer user_data);
55 : :
56 : : /**
57 : : * MctWebService:
58 : : *
59 : : * The core implementation of the web filtering daemon, which exposes its D-Bus
60 : : * API on the bus.
61 : : *
62 : : * Since: 0.14.0
63 : : */
64 : : struct _MctWebService
65 : : {
66 : : GssService parent;
67 : :
68 : : char *state_directory_path; /* (owned) (nullable) */
69 : : char *cache_directory_path; /* (owned) (nullable) */
70 : : MctFilteringDBusService *filtering_dbus_service; /* (owned) */
71 : : unsigned long filtering_dbus_service_notify_busy_id;
72 : :
73 : : gboolean busy;
74 : : };
75 : :
76 [ + + + - : 11 : G_DEFINE_TYPE (MctWebService, mct_web_service, GSS_TYPE_SERVICE)
+ + ]
77 : :
78 : : static void
79 : 1 : mct_web_service_class_init (MctWebServiceClass *klass)
80 : : {
81 : 1 : GObjectClass *object_class = (GObjectClass *) klass;
82 : 1 : GssServiceClass *service_class = (GssServiceClass *) klass;
83 : :
84 : 1 : object_class->dispose = mct_web_service_dispose;
85 : 1 : object_class->finalize = mct_web_service_finalize;
86 : :
87 : 1 : service_class->startup_async = mct_web_service_startup_async;
88 : 1 : service_class->startup_finish = mct_web_service_startup_finish;
89 : 1 : service_class->shutdown = mct_web_service_shutdown;
90 : 1 : }
91 : :
92 : : static void
93 : 1 : mct_web_service_init (MctWebService *self)
94 : : {
95 : 1 : g_autoptr(GOptionGroup) group = NULL;
96 : 1 : const GOptionEntry options[] =
97 : : {
98 : 1 : { "state-directory", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &self->state_directory_path,
99 : : N_("Directory to store data in (default: $STATE_DIRECTORY)"), N_("DIR") },
100 : 1 : { "cache-directory", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &self->cache_directory_path,
101 : : N_("Directory to cache data in (default: $CACHE_DIRECTORY)"), N_("DIR") },
102 : : { NULL}
103 : : };
104 : :
105 : : /* Add an additional command line option for the state directory. */
106 : 1 : group = g_option_group_new ("service", _("Service Options:"), _("Show service help options"), NULL, NULL);
107 : 1 : g_option_group_add_entries (group, options);
108 : 1 : gss_service_add_option_group (GSS_SERVICE (self), group);
109 : 1 : }
110 : :
111 : : static void
112 : 1 : mct_web_service_dispose (GObject *object)
113 : : {
114 : 1 : MctWebService *self = MCT_WEB_SERVICE (object);
115 : :
116 [ + - ]: 1 : g_clear_signal_handler (&self->filtering_dbus_service_notify_busy_id, self->filtering_dbus_service);
117 : :
118 [ + - ]: 1 : g_clear_object (&self->filtering_dbus_service);
119 : :
120 : : /* Chain up to the parent class */
121 : 1 : G_OBJECT_CLASS (mct_web_service_parent_class)->dispose (object);
122 : 1 : }
123 : :
124 : : static void
125 : 1 : mct_web_service_finalize (GObject *object)
126 : : {
127 : 1 : MctWebService *self = MCT_WEB_SERVICE (object);
128 : :
129 [ + - ]: 1 : g_clear_pointer (&self->state_directory_path, g_free);
130 [ + - ]: 1 : g_clear_pointer (&self->cache_directory_path, g_free);
131 : :
132 : : /* Chain up to the parent class */
133 : 1 : G_OBJECT_CLASS (mct_web_service_parent_class)->finalize (object);
134 : 1 : }
135 : :
136 : : static GFile *
137 : 2 : get_system_directory (const char *environment_variable_name,
138 : : const char *command_line_argument)
139 : : {
140 : 2 : const char *env = g_getenv (environment_variable_name);
141 [ + - ]: 2 : if (command_line_argument != NULL)
142 : 2 : return g_file_new_for_commandline_arg (command_line_argument);
143 [ # # ]: 0 : else if (env != NULL)
144 : 0 : return g_file_new_for_commandline_arg (env);
145 : : else
146 : 0 : return NULL;
147 : : }
148 : :
149 : : typedef struct
150 : : {
151 : : GFile *state_directory;
152 : : GFile *cache_directory;
153 : : MctManager *policy_manager;
154 : : MctUserManager *user_manager;
155 : : } StartupData;
156 : :
157 : : static void
158 : 1 : startup_data_free (StartupData *data)
159 : : {
160 [ + - ]: 1 : g_clear_object (&data->user_manager);
161 [ + - ]: 1 : g_clear_object (&data->policy_manager);
162 [ + - ]: 1 : g_clear_object (&data->cache_directory);
163 [ + - ]: 1 : g_clear_object (&data->state_directory);
164 : 1 : g_free (data);
165 : 1 : }
166 : :
167 [ - + ]: 2 : G_DEFINE_AUTOPTR_CLEANUP_FUNC (StartupData, startup_data_free)
168 : :
169 : : static void user_manager_loaded_cb (GObject *object,
170 : : GAsyncResult *result,
171 : : void *user_data);
172 : :
173 : : static void
174 : 1 : mct_web_service_startup_async (GssService *service,
175 : : GCancellable *cancellable,
176 : : GAsyncReadyCallback callback,
177 : : gpointer user_data)
178 : : {
179 : 1 : MctWebService *self = MCT_WEB_SERVICE (service);
180 [ + - ]: 1 : g_autoptr(GTask) task = NULL;
181 [ + - ]: 1 : g_autoptr(StartupData) data_owned = NULL;
182 : : StartupData *data;
183 : 1 : GDBusConnection *connection = gss_service_get_dbus_connection (service);
184 [ + - ]: 1 : g_autoptr(GError) local_error = NULL;
185 : :
186 : 1 : task = g_task_new (service, cancellable, callback, user_data);
187 [ + - ]: 1 : g_task_set_source_tag (task, mct_web_service_startup_async);
188 : :
189 : 1 : data = data_owned = g_new0 (StartupData, 1);
190 : 1 : g_task_set_task_data (task, g_steal_pointer (&data_owned), (GDestroyNotify) startup_data_free);
191 : :
192 : : /* Work out where the state directory is. If running under systemd, it’ll be
193 : : * passed in as `STATE_DIRECTORY` due to our use of the `StateDirectory=`
194 : : * option (see systemd.exec(5)). We prefer the command line option, if
195 : : * specified, as that’s used for debugging. */
196 : 1 : data->state_directory = get_system_directory ("STATE_DIRECTORY", self->state_directory_path);
197 [ - + ]: 1 : if (data->state_directory == NULL)
198 : : {
199 : 0 : g_task_return_new_error (task, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_INVALID_ENVIRONMENT,
200 : 0 : _("No state directory specified using $STATE_DIRECTORY or --state-directory"));
201 : 0 : return;
202 : : }
203 : :
204 : : /* Similarly work out the cache directory. */
205 : 1 : data->cache_directory = get_system_directory ("CACHE_DIRECTORY", self->cache_directory_path);
206 [ - + ]: 1 : if (data->cache_directory == NULL)
207 : : {
208 : 0 : g_task_return_new_error (task, GSS_SERVICE_ERROR, GSS_SERVICE_ERROR_INVALID_ENVIRONMENT,
209 : 0 : _("No cache directory specified using $CACHE_DIRECTORY or --cache-directory"));
210 : 0 : return;
211 : : }
212 : :
213 : 1 : data->policy_manager = mct_manager_new (connection);
214 : 1 : data->user_manager = mct_user_manager_new (connection);
215 : :
216 : : /* Fill the user data cache */
217 : 1 : mct_user_manager_load_async (data->user_manager, cancellable,
218 : 1 : user_manager_loaded_cb, g_steal_pointer (&task));
219 : : }
220 : :
221 : : static void
222 : 1 : user_manager_loaded_cb (GObject *object,
223 : : GAsyncResult *result,
224 : : void *user_data)
225 : : {
226 : 1 : MctUserManager *user_manager = MCT_USER_MANAGER (object);
227 [ + - ]: 2 : g_autoptr(GTask) task = G_TASK (user_data);
228 : 1 : MctWebService *self = MCT_WEB_SERVICE (g_task_get_source_object (task));
229 : 1 : GDBusConnection *connection = gss_service_get_dbus_connection (GSS_SERVICE (self));
230 : 1 : StartupData *data = g_task_get_task_data (task);
231 [ + - ]: 1 : g_autoptr(MctFilterUpdater) filter_updater = NULL;
232 [ + - ]: 1 : g_autoptr(GError) local_error = NULL;
233 : :
234 [ - + ]: 1 : if (!mct_user_manager_load_finish (user_manager, result, &local_error))
235 : : {
236 : 0 : g_task_return_error (task, g_steal_pointer (&local_error));
237 : 0 : return;
238 : : }
239 : :
240 : 1 : filter_updater = mct_filter_updater_new (data->policy_manager, data->user_manager,
241 : : data->state_directory, data->cache_directory);
242 : 1 : self->filtering_dbus_service =
243 : 1 : mct_filtering_dbus_service_new (connection,
244 : : "/org/freedesktop/MalcontentWeb1",
245 : : filter_updater);
246 : 1 : self->filtering_dbus_service_notify_busy_id =
247 : 1 : g_signal_connect (self->filtering_dbus_service, "notify::busy",
248 : : (GCallback) notify_busy_cb, self);
249 : 1 : notify_busy_cb (G_OBJECT (self->filtering_dbus_service), NULL, self);
250 : :
251 [ - + ]: 1 : if (!mct_filtering_dbus_service_register (self->filtering_dbus_service, &local_error))
252 : 0 : g_task_return_error (task, g_steal_pointer (&local_error));
253 : : else
254 : 1 : g_task_return_boolean (task, TRUE);
255 : : }
256 : :
257 : : static void
258 : 1 : mct_web_service_startup_finish (GssService *service,
259 : : GAsyncResult *result,
260 : : GError **error)
261 : : {
262 : 1 : g_task_propagate_boolean (G_TASK (result), error);
263 : 1 : }
264 : :
265 : : static void
266 : 3 : notify_busy_cb (GObject *obj,
267 : : GParamSpec *pspec,
268 : : gpointer user_data)
269 : : {
270 : 3 : MctWebService *self = MCT_WEB_SERVICE (user_data);
271 : :
272 : 3 : gboolean was_busy = self->busy;
273 : 3 : gboolean now_busy = mct_filtering_dbus_service_get_busy (self->filtering_dbus_service);
274 : :
275 [ - + - + ]: 3 : g_debug ("%s: was_busy: %s, now_busy: %s",
276 : : G_STRFUNC, was_busy ? "yes" : "no", now_busy ? "yes" : "no");
277 : :
278 [ - + - - ]: 3 : if (was_busy && !now_busy)
279 : 0 : gss_service_release (GSS_SERVICE (self));
280 [ + - - + ]: 3 : else if (!was_busy && now_busy)
281 : 0 : gss_service_hold (GSS_SERVICE (self));
282 : :
283 : 3 : self->busy = now_busy;
284 : 3 : }
285 : :
286 : : static void
287 : 1 : mct_web_service_shutdown (GssService *service)
288 : : {
289 : 1 : MctWebService *self = MCT_WEB_SERVICE (service);
290 : :
291 : 1 : mct_filtering_dbus_service_unregister (self->filtering_dbus_service);
292 : 1 : }
293 : :
294 : : /**
295 : : * mct_web_service_new:
296 : : *
297 : : * Create a new [class@Malcontent.WebService].
298 : : *
299 : : * Returns: (transfer full): a new [class@Malcontent.WebService]
300 : : * Since: 0.14.0
301 : : */
302 : : MctWebService *
303 : 1 : mct_web_service_new (void)
304 : : {
305 : 1 : return g_object_new (MCT_TYPE_WEB_SERVICE,
306 : : "bus-type", G_BUS_TYPE_SYSTEM,
307 : : "service-id", "org.freedesktop.MalcontentWeb1",
308 : : "inactivity-timeout", 30000 /* ms */,
309 : : "translation-domain", GETTEXT_PACKAGE,
310 : 1 : "parameter-string", _("— manage web filter lists for parental controls"),
311 : 1 : "summary", _("Manage web filter lists to enforce "
312 : : "parental controls on web access."),
313 : : NULL);
314 : : }
315 : :
|