1 /* GIO - GLib Input, Output and Streaming Library
2  *
3  * Copyright 2016 Red Hat, 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
16  * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "config.h"
20 
21 #include "gnetworkmonitorportal.h"
22 #include "ginitable.h"
23 #include "giomodule-priv.h"
24 #include "xdp-dbus.h"
25 #include "gportalsupport.h"
26 
27 static GInitableIface *initable_parent_iface;
28 static void g_network_monitor_portal_iface_init (GNetworkMonitorInterface *iface);
29 static void g_network_monitor_portal_initable_iface_init (GInitableIface *iface);
30 
31 enum
32 {
33   PROP_0,
34   PROP_NETWORK_AVAILABLE,
35   PROP_NETWORK_METERED,
36   PROP_CONNECTIVITY
37 };
38 
39 struct _GNetworkMonitorPortalPrivate
40 {
41   GDBusProxy *proxy;
42   gboolean has_network;
43 
44   gboolean available;
45   gboolean metered;
46   GNetworkConnectivity connectivity;
47 };
48 
49 G_DEFINE_TYPE_WITH_CODE (GNetworkMonitorPortal, g_network_monitor_portal, G_TYPE_NETWORK_MONITOR_BASE,
50                          G_ADD_PRIVATE (GNetworkMonitorPortal)
51                          G_IMPLEMENT_INTERFACE (G_TYPE_NETWORK_MONITOR,
52                                                 g_network_monitor_portal_iface_init)
53                          G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
54                                                 g_network_monitor_portal_initable_iface_init)
55                          _g_io_modules_ensure_extension_points_registered ();
56                          g_io_extension_point_implement (G_NETWORK_MONITOR_EXTENSION_POINT_NAME,
57                                                          g_define_type_id,
58                                                          "portal",
59                                                          40))
60 
61 static void
g_network_monitor_portal_init(GNetworkMonitorPortal * nm)62 g_network_monitor_portal_init (GNetworkMonitorPortal *nm)
63 {
64   nm->priv = g_network_monitor_portal_get_instance_private (nm);
65 }
66 
67 static void
g_network_monitor_portal_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)68 g_network_monitor_portal_get_property (GObject    *object,
69                                        guint       prop_id,
70                                        GValue     *value,
71                                        GParamSpec *pspec)
72 {
73   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (object);
74 
75   switch (prop_id)
76     {
77     case PROP_NETWORK_AVAILABLE:
78       g_value_set_boolean (value, nm->priv->available);
79       break;
80 
81     case PROP_NETWORK_METERED:
82       g_value_set_boolean (value, nm->priv->metered);
83       break;
84 
85     case PROP_CONNECTIVITY:
86       g_value_set_enum (value, nm->priv->connectivity);
87       break;
88 
89     default:
90       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
91       break;
92     }
93 }
94 
95 static gboolean
is_valid_connectivity(guint32 value)96 is_valid_connectivity (guint32 value)
97 {
98   GEnumValue *enum_value;
99   GEnumClass *enum_klass;
100 
101   enum_klass = g_type_class_ref (G_TYPE_NETWORK_CONNECTIVITY);
102   enum_value = g_enum_get_value (enum_klass, value);
103 
104   g_type_class_unref (enum_klass);
105 
106   return enum_value != NULL;
107 }
108 
109 static void
got_available(GObject * source,GAsyncResult * res,gpointer data)110 got_available (GObject *source,
111                GAsyncResult *res,
112                gpointer data)
113 {
114   GDBusProxy *proxy = G_DBUS_PROXY (source);
115   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
116   GError *error = NULL;
117   GVariant *ret;
118   gboolean available;
119 
120   ret = g_dbus_proxy_call_finish (proxy, res, &error);
121   if (ret == NULL)
122     {
123       if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
124         {
125           g_warning ("%s", error->message);
126           g_clear_error (&error);
127           return;
128         }
129 
130       g_clear_error (&error);
131 
132       /* Fall back to version 1 */
133       ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "available");
134       if (ret == NULL)
135         {
136           g_warning ("Failed to get the '%s' property", "available");
137           return;
138         }
139 
140       available = g_variant_get_boolean (ret);
141       g_variant_unref (ret);
142     }
143   else
144     {
145       g_variant_get (ret, "(b)", &available);
146       g_variant_unref (ret);
147     }
148 
149   if (nm->priv->available != available)
150     {
151       nm->priv->available = available;
152       g_object_notify (G_OBJECT (nm), "network-available");
153       g_signal_emit_by_name (nm, "network-changed", available);
154     }
155 }
156 
157 static void
got_metered(GObject * source,GAsyncResult * res,gpointer data)158 got_metered (GObject *source,
159              GAsyncResult *res,
160              gpointer data)
161 {
162   GDBusProxy *proxy = G_DBUS_PROXY (source);
163   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
164   GError *error = NULL;
165   GVariant *ret;
166   gboolean metered;
167 
168   ret = g_dbus_proxy_call_finish (proxy, res, &error);
169   if (ret == NULL)
170     {
171       if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
172         {
173           g_warning ("%s", error->message);
174           g_clear_error (&error);
175           return;
176         }
177 
178       g_clear_error (&error);
179 
180       /* Fall back to version 1 */
181       ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "metered");
182       if (ret == NULL)
183         {
184           g_warning ("Failed to get the '%s' property", "metered");
185           return;
186         }
187 
188       metered = g_variant_get_boolean (ret);
189       g_variant_unref (ret);
190     }
191   else
192     {
193       g_variant_get (ret, "(b)", &metered);
194       g_variant_unref (ret);
195     }
196 
197   if (nm->priv->metered != metered)
198     {
199       nm->priv->metered = metered;
200       g_object_notify (G_OBJECT (nm), "network-metered");
201       g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
202     }
203 }
204 
205 static void
got_connectivity(GObject * source,GAsyncResult * res,gpointer data)206 got_connectivity (GObject *source,
207                   GAsyncResult *res,
208                   gpointer data)
209 {
210   GDBusProxy *proxy = G_DBUS_PROXY (source);
211   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
212   GError *error = NULL;
213   GVariant *ret;
214   GNetworkConnectivity connectivity;
215 
216   ret = g_dbus_proxy_call_finish (proxy, res, &error);
217   if (ret == NULL)
218     {
219       if (!g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
220         {
221           g_warning ("%s", error->message);
222           g_clear_error (&error);
223           return;
224         }
225 
226       g_clear_error (&error);
227 
228       /* Fall back to version 1 */
229       ret = g_dbus_proxy_get_cached_property (nm->priv->proxy, "connectivity");
230       if (ret == NULL)
231         {
232           g_warning ("Failed to get the '%s' property", "connectivity");
233           return;
234         }
235 
236       connectivity = g_variant_get_uint32 (ret);
237       g_variant_unref (ret);
238     }
239   else
240     {
241       g_variant_get (ret, "(u)", &connectivity);
242       g_variant_unref (ret);
243     }
244 
245   if (nm->priv->connectivity != connectivity &&
246       is_valid_connectivity (connectivity))
247     {
248       nm->priv->connectivity = connectivity;
249       g_object_notify (G_OBJECT (nm), "connectivity");
250       g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
251     }
252 }
253 
254 static void
got_status(GObject * source,GAsyncResult * res,gpointer data)255 got_status (GObject *source,
256             GAsyncResult *res,
257             gpointer data)
258 {
259   GDBusProxy *proxy = G_DBUS_PROXY (source);
260   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (data);
261   GError *error = NULL;
262   GVariant *ret;
263   GVariant *status;
264   gboolean available;
265   gboolean metered;
266   GNetworkConnectivity connectivity;
267 
268   ret = g_dbus_proxy_call_finish (proxy, res, &error);
269   if (ret == NULL)
270     {
271       if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_METHOD))
272         {
273           /* Fall back to version 2 */
274           g_dbus_proxy_call (proxy, "GetConnectivity", NULL, 0, -1, NULL, got_connectivity, nm);
275           g_dbus_proxy_call (proxy, "GetMetered", NULL, 0, -1, NULL, got_metered, nm);
276           g_dbus_proxy_call (proxy, "GetAvailable", NULL, 0, -1, NULL, got_available, nm);
277         }
278       else
279         g_warning ("%s", error->message);
280 
281       g_clear_error (&error);
282       return;
283     }
284 
285   g_variant_get (ret, "(@a{sv})", &status);
286   g_variant_unref (ret);
287 
288   g_variant_lookup (status, "available", "b", &available);
289   g_variant_lookup (status, "metered", "b", &metered);
290   g_variant_lookup (status, "connectivity", "u", &connectivity);
291   g_variant_unref (status);
292 
293   g_object_freeze_notify (G_OBJECT (nm));
294 
295   if (nm->priv->available != available)
296     {
297       nm->priv->available = available;
298       g_object_notify (G_OBJECT (nm), "network-available");
299     }
300 
301   if (nm->priv->metered != metered)
302     {
303       nm->priv->metered = metered;
304       g_object_notify (G_OBJECT (nm), "network-metered");
305     }
306 
307   if (nm->priv->connectivity != connectivity &&
308       is_valid_connectivity (connectivity))
309     {
310       nm->priv->connectivity = connectivity;
311       g_object_notify (G_OBJECT (nm), "connectivity");
312     }
313 
314   g_object_thaw_notify (G_OBJECT (nm));
315 
316   g_signal_emit_by_name (nm, "network-changed", available);
317 }
318 
319 static void
update_properties(GDBusProxy * proxy,GNetworkMonitorPortal * nm)320 update_properties (GDBusProxy *proxy,
321                    GNetworkMonitorPortal *nm)
322 {
323   /* Try version 3 first */
324   g_dbus_proxy_call (proxy, "GetStatus", NULL, 0, -1, NULL, got_status, nm);
325 }
326 
327 static void
proxy_signal(GDBusProxy * proxy,const char * sender,const char * signal,GVariant * parameters,GNetworkMonitorPortal * nm)328 proxy_signal (GDBusProxy            *proxy,
329               const char            *sender,
330               const char            *signal,
331               GVariant              *parameters,
332               GNetworkMonitorPortal *nm)
333 {
334   if (!nm->priv->has_network)
335     return;
336 
337   if (strcmp (signal, "changed") != 0)
338     return;
339 
340   /* Version 1 updates "available" with the "changed" signal */
341   if (g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(b)")))
342     {
343       gboolean available;
344 
345       g_variant_get (parameters, "(b)", &available);
346       if (nm->priv->available != available)
347         {
348           nm->priv->available = available;
349           g_object_notify (G_OBJECT (nm), "available");
350         }
351       g_signal_emit_by_name (nm, "network-changed", available);
352     }
353   else
354     {
355       update_properties (proxy, nm);
356     }
357 }
358 
359 static void
proxy_properties_changed(GDBusProxy * proxy,GVariant * changed,GVariant * invalidated,GNetworkMonitorPortal * nm)360 proxy_properties_changed (GDBusProxy            *proxy,
361                           GVariant              *changed,
362                           GVariant              *invalidated,
363                           GNetworkMonitorPortal *nm)
364 {
365   gboolean should_emit_changed = FALSE;
366   GVariant *ret;
367 
368   if (!nm->priv->has_network)
369     return;
370 
371   ret = g_dbus_proxy_get_cached_property (proxy, "connectivity");
372   if (ret)
373     {
374       GNetworkConnectivity connectivity = g_variant_get_uint32 (ret);
375       if (nm->priv->connectivity != connectivity &&
376           is_valid_connectivity (connectivity))
377         {
378           nm->priv->connectivity = connectivity;
379           g_object_notify (G_OBJECT (nm), "connectivity");
380           should_emit_changed = TRUE;
381         }
382       g_variant_unref (ret);
383     }
384 
385   ret = g_dbus_proxy_get_cached_property (proxy, "metered");
386   if (ret)
387     {
388       gboolean metered = g_variant_get_boolean (ret);
389       if (nm->priv->metered != metered)
390         {
391           nm->priv->metered = metered;
392           g_object_notify (G_OBJECT (nm), "network-metered");
393           should_emit_changed = TRUE;
394         }
395       g_variant_unref (ret);
396     }
397 
398   ret = g_dbus_proxy_get_cached_property (proxy, "available");
399   if (ret)
400     {
401       gboolean available = g_variant_get_boolean (ret);
402       if (nm->priv->available != available)
403         {
404           nm->priv->available = available;
405           g_object_notify (G_OBJECT (nm), "network-available");
406           should_emit_changed = TRUE;
407         }
408       g_variant_unref (ret);
409     }
410 
411   if (should_emit_changed)
412     g_signal_emit_by_name (nm, "network-changed", nm->priv->available);
413 }
414 
415 static gboolean
g_network_monitor_portal_initable_init(GInitable * initable,GCancellable * cancellable,GError ** error)416 g_network_monitor_portal_initable_init (GInitable     *initable,
417                                         GCancellable  *cancellable,
418                                         GError       **error)
419 {
420   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (initable);
421   GDBusProxy *proxy;
422   gchar *name_owner = NULL;
423 
424   nm->priv->available = FALSE;
425   nm->priv->metered = FALSE;
426   nm->priv->connectivity = G_NETWORK_CONNECTIVITY_LOCAL;
427 
428   if (!glib_should_use_portal ())
429     {
430       g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Not using portals");
431       return FALSE;
432     }
433 
434   proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
435                                          G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
436                                          NULL,
437                                          "org.freedesktop.portal.Desktop",
438                                          "/org/freedesktop/portal/desktop",
439                                          "org.freedesktop.portal.NetworkMonitor",
440                                          cancellable,
441                                          error);
442   if (!proxy)
443     return FALSE;
444 
445   name_owner = g_dbus_proxy_get_name_owner (proxy);
446 
447   if (!name_owner)
448     {
449       g_object_unref (proxy);
450       g_set_error (error,
451                    G_DBUS_ERROR,
452                    G_DBUS_ERROR_NAME_HAS_NO_OWNER,
453                    "Desktop portal not found");
454       return FALSE;
455     }
456 
457   g_free (name_owner);
458 
459   g_signal_connect (proxy, "g-signal", G_CALLBACK (proxy_signal), nm);
460   g_signal_connect (proxy, "g-properties-changed", G_CALLBACK (proxy_properties_changed), nm);
461 
462   nm->priv->proxy = proxy;
463   nm->priv->has_network = glib_network_available_in_sandbox ();
464 
465   if (!initable_parent_iface->init (initable, cancellable, error))
466     return FALSE;
467 
468   if (nm->priv->has_network)
469     update_properties (proxy, nm);
470 
471   return TRUE;
472 }
473 
474 static void
g_network_monitor_portal_finalize(GObject * object)475 g_network_monitor_portal_finalize (GObject *object)
476 {
477   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (object);
478 
479   g_clear_object (&nm->priv->proxy);
480 
481   G_OBJECT_CLASS (g_network_monitor_portal_parent_class)->finalize (object);
482 }
483 
484 static void
g_network_monitor_portal_class_init(GNetworkMonitorPortalClass * class)485 g_network_monitor_portal_class_init (GNetworkMonitorPortalClass *class)
486 {
487   GObjectClass *gobject_class = G_OBJECT_CLASS (class);
488 
489   gobject_class->finalize  = g_network_monitor_portal_finalize;
490   gobject_class->get_property = g_network_monitor_portal_get_property;
491 
492   g_object_class_override_property (gobject_class, PROP_NETWORK_AVAILABLE, "network-available");
493   g_object_class_override_property (gobject_class, PROP_NETWORK_METERED, "network-metered");
494   g_object_class_override_property (gobject_class, PROP_CONNECTIVITY, "connectivity");
495 }
496 
497 static gboolean
g_network_monitor_portal_can_reach(GNetworkMonitor * monitor,GSocketConnectable * connectable,GCancellable * cancellable,GError ** error)498 g_network_monitor_portal_can_reach (GNetworkMonitor     *monitor,
499                                     GSocketConnectable  *connectable,
500                                     GCancellable        *cancellable,
501                                     GError             **error)
502 {
503   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (monitor);
504   GVariant *ret;
505   GNetworkAddress *address;
506   gboolean reachable = FALSE;
507 
508   if (!G_IS_NETWORK_ADDRESS (connectable))
509     {
510       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
511                    "Can't handle this kind of GSocketConnectable (%s)",
512                    G_OBJECT_TYPE_NAME (connectable));
513       return FALSE;
514     }
515 
516   address = G_NETWORK_ADDRESS (connectable);
517 
518   ret = g_dbus_proxy_call_sync (nm->priv->proxy,
519                                 "CanReach",
520                                 g_variant_new ("(su)",
521                                                g_network_address_get_hostname (address),
522                                                g_network_address_get_port (address)),
523                                 G_DBUS_CALL_FLAGS_NONE,
524                                 -1,
525                                 cancellable,
526                                 error);
527 
528   if (ret)
529     {
530       g_variant_get (ret, "(b)", &reachable);
531       g_variant_unref (ret);
532     }
533 
534   return reachable;
535 }
536 
537 static void
can_reach_done(GObject * source,GAsyncResult * result,gpointer data)538 can_reach_done (GObject      *source,
539                 GAsyncResult *result,
540                 gpointer      data)
541 {
542   GTask *task = data;
543   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (g_task_get_source_object (task));
544   GError *error = NULL;
545   GVariant *ret;
546   gboolean reachable;
547 
548   ret = g_dbus_proxy_call_finish (nm->priv->proxy, result, &error);
549   if (ret == NULL)
550     {
551       g_task_return_error (task, error);
552       g_object_unref (task);
553       return;
554     }
555 
556   g_variant_get (ret, "(b)", &reachable);
557   g_variant_unref (ret);
558 
559   if (reachable)
560     g_task_return_boolean (task, TRUE);
561   else
562     g_task_return_new_error (task,
563                              G_IO_ERROR, G_IO_ERROR_HOST_UNREACHABLE,
564                              "Can't reach host");
565 
566   g_object_unref (task);
567 }
568 
569 static void
g_network_monitor_portal_can_reach_async(GNetworkMonitor * monitor,GSocketConnectable * connectable,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer data)570 g_network_monitor_portal_can_reach_async (GNetworkMonitor     *monitor,
571                                           GSocketConnectable  *connectable,
572                                           GCancellable        *cancellable,
573                                           GAsyncReadyCallback  callback,
574                                           gpointer             data)
575 {
576   GNetworkMonitorPortal *nm = G_NETWORK_MONITOR_PORTAL (monitor);
577   GTask *task;
578   GNetworkAddress *address;
579 
580   task = g_task_new (monitor, cancellable, callback, data);
581 
582   if (!G_IS_NETWORK_ADDRESS (connectable))
583     {
584       g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
585                                "Can't handle this kind of GSocketConnectable (%s)",
586                                G_OBJECT_TYPE_NAME (connectable));
587       g_object_unref (task);
588       return;
589     }
590 
591   address = G_NETWORK_ADDRESS (connectable);
592 
593   g_dbus_proxy_call (nm->priv->proxy,
594                      "CanReach",
595                      g_variant_new ("(su)",
596                                     g_network_address_get_hostname (address),
597                                     g_network_address_get_port (address)),
598                      G_DBUS_CALL_FLAGS_NONE,
599                      -1,
600                      cancellable,
601                      can_reach_done,
602                      task);
603 }
604 
605 static gboolean
g_network_monitor_portal_can_reach_finish(GNetworkMonitor * monitor,GAsyncResult * result,GError ** error)606 g_network_monitor_portal_can_reach_finish (GNetworkMonitor  *monitor,
607                                            GAsyncResult     *result,
608                                            GError          **error)
609 {
610   return g_task_propagate_boolean (G_TASK (result), error);
611 }
612 
613 static void
g_network_monitor_portal_iface_init(GNetworkMonitorInterface * monitor_iface)614 g_network_monitor_portal_iface_init (GNetworkMonitorInterface *monitor_iface)
615 {
616   monitor_iface->can_reach = g_network_monitor_portal_can_reach;
617   monitor_iface->can_reach_async = g_network_monitor_portal_can_reach_async;
618   monitor_iface->can_reach_finish = g_network_monitor_portal_can_reach_finish;
619 }
620 
621 static void
g_network_monitor_portal_initable_iface_init(GInitableIface * iface)622 g_network_monitor_portal_initable_iface_init (GInitableIface *iface)
623 {
624   initable_parent_iface = g_type_interface_peek_parent (iface);
625 
626   iface->init = g_network_monitor_portal_initable_init;
627 }
628