device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / nm-firewall-manager.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * Copyright (C) 2011 - 2015 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include "nm-firewall-manager.h"
24
25 #include <string.h>
26
27 #include "NetworkManagerUtils.h"
28
29 #define NM_FIREWALL_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \
30                                               NM_TYPE_FIREWALL_MANAGER, \
31                                               NMFirewallManagerPrivate))
32
33 G_DEFINE_TYPE (NMFirewallManager, nm_firewall_manager, G_TYPE_OBJECT)
34
35 /* Properties */
36 enum {
37         PROP_0 = 0,
38         PROP_AVAILABLE,
39         LAST_PROP
40 };
41
42 typedef struct {
43         GDBusProxy *    proxy;
44         gboolean        running;
45
46         GHashTable     *pending_calls;
47 } NMFirewallManagerPrivate;
48
49 enum {
50         STARTED,
51
52         LAST_SIGNAL
53 };
54
55 static guint signals[LAST_SIGNAL] = { 0 };
56
57 NM_DEFINE_SINGLETON_GETTER (NMFirewallManager, nm_firewall_manager_get, NM_TYPE_FIREWALL_MANAGER);
58
59 /********************************************************************/
60
61 typedef enum {
62         CB_INFO_OPS_ADD = 1,
63         CB_INFO_OPS_CHANGE,
64         CB_INFO_OPS_REMOVE,
65 } CBInfoOpsType;
66
67 typedef enum {
68         CB_INFO_MODE_IDLE = 1,
69         CB_INFO_MODE_DBUS,
70         CB_INFO_MODE_DBUS_COMPLETED,
71 } CBInfoMode;
72
73 struct _NMFirewallManagerCallId {
74         NMFirewallManager *self;
75         CBInfoOpsType ops_type;
76         CBInfoMode mode;
77         char *iface;
78         NMFirewallManagerAddRemoveCallback callback;
79         gpointer user_data;
80
81         union {
82                 struct {
83                         GCancellable *cancellable;
84                 } dbus;
85                 struct {
86                         guint id;
87                 } idle;
88         };
89 };
90 typedef struct _NMFirewallManagerCallId CBInfo;
91
92 /********************************************************************/
93
94 static const char *
95 _ops_type_to_string (CBInfoOpsType ops_type)
96 {
97         switch (ops_type) {
98         case CB_INFO_OPS_ADD:    return "add";
99         case CB_INFO_OPS_REMOVE: return "remove";
100         case CB_INFO_OPS_CHANGE: return "change";
101         default: g_return_val_if_reached ("unknown");
102         }
103 }
104
105 #define _NMLOG_DOMAIN      LOGD_FIREWALL
106 #define _NMLOG_PREFIX_NAME "firewall"
107 #define _NMLOG(level, info, ...) \
108     G_STMT_START { \
109         if (nm_logging_enabled ((level), (_NMLOG_DOMAIN))) { \
110             CBInfo *__info = (info); \
111             char __prefix_name[30]; \
112             char __prefix_info[64]; \
113             \
114             _nm_log ((level), (_NMLOG_DOMAIN), 0, \
115                      "%s: %s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
116                      (self) != singleton_instance \
117                         ? ({ \
118                                 g_snprintf (__prefix_name, sizeof (__prefix_name), "%s[%p]", ""_NMLOG_PREFIX_NAME, (self)); \
119                                 __prefix_name; \
120                            }) \
121                         : _NMLOG_PREFIX_NAME, \
122                      __info \
123                         ? ({ \
124                                 g_snprintf (__prefix_info, sizeof (__prefix_info), "[%p,%s%s:%s%s%s]: ", __info, \
125                                             _ops_type_to_string (__info->ops_type), _cb_info_is_idle (__info) ? "*" : "", \
126                                             NM_PRINT_FMT_QUOTE_STRING (__info->iface)); \
127                                 __prefix_info; \
128                            }) \
129                         : "" \
130                      _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
131         } \
132     } G_STMT_END
133
134 /********************************************************************/
135
136 static gboolean
137 _cb_info_is_idle (CBInfo *info)
138 {
139         return info->mode == CB_INFO_MODE_IDLE;
140 }
141
142 static CBInfo *
143 _cb_info_create (NMFirewallManager *self,
144                  CBInfoOpsType ops_type,
145                  const char *iface,
146                  NMFirewallManagerAddRemoveCallback callback,
147                  gpointer user_data)
148 {
149         NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
150         CBInfo *info;
151
152         info = g_slice_new0 (CBInfo);
153         info->self = g_object_ref (self);
154         info->ops_type = ops_type;
155         info->iface = g_strdup (iface);
156         info->callback = callback;
157         info->user_data = user_data;
158
159         if (priv->running) {
160                 info->mode = CB_INFO_MODE_DBUS;
161                 info->dbus.cancellable = g_cancellable_new ();
162         } else
163                 info->mode = CB_INFO_MODE_IDLE;
164
165         if (!nm_g_hash_table_add (priv->pending_calls, info))
166                 g_return_val_if_reached (NULL);
167
168         return info;
169 }
170
171 static void
172 _cb_info_free (CBInfo *info)
173 {
174         if (!_cb_info_is_idle (info))
175                 g_object_unref (info->dbus.cancellable);
176         g_free (info->iface);
177         if (info->self)
178                 g_object_unref (info->self);
179         g_slice_free (CBInfo, info);
180 }
181
182 static void
183 _cb_info_callback (CBInfo *info,
184                    GError *error)
185 {
186         if (info->callback)
187                 info->callback (info->self, info, error, info->user_data);
188 }
189
190 static void
191 _cb_info_complete_normal (CBInfo *info, GError *error)
192 {
193         NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (info->self);
194
195         if (!g_hash_table_remove (priv->pending_calls, info))
196                 g_return_if_reached ();
197
198         _cb_info_callback (info, error);
199         _cb_info_free (info);
200 }
201
202 static gboolean
203 _handle_idle (gpointer user_data)
204 {
205         NMFirewallManager *self;
206         CBInfo *info = user_data;
207
208         nm_assert (info && NM_IS_FIREWALL_MANAGER (info->self));
209
210         self = info->self;
211
212         _LOGD (info, "complete: fake success");
213
214         _cb_info_complete_normal (info, NULL);
215         return G_SOURCE_REMOVE;
216 }
217
218 static void
219 _handle_dbus (GObject *proxy, GAsyncResult *result, gpointer user_data)
220 {
221         NMFirewallManager *self;
222         CBInfo *info = user_data;
223         gs_free_error GError *error = NULL;
224         gs_unref_variant GVariant *ret = NULL;
225
226         if (info->mode != CB_INFO_MODE_DBUS) {
227                 _cb_info_free (info);
228                 return;
229         }
230
231         self = info->self;
232
233         ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), result, &error);
234
235         if (error) {
236                 const char *non_error = NULL;
237
238                 g_dbus_error_strip_remote_error (error);
239
240                 switch (info->ops_type) {
241                 case CB_INFO_OPS_ADD:
242                 case CB_INFO_OPS_CHANGE:
243                         non_error = "ZONE_ALREADY_SET";
244                         break;
245                 case CB_INFO_OPS_REMOVE:
246                         non_error = "UNKNOWN_INTERFACE";
247                         break;
248                 }
249                 if (!g_strcmp0 (error->message, non_error)) {
250                         _LOGD (info, "complete: request failed with a non-error (%s)", error->message);
251
252                         /* The operation failed with an error reason that we don't want
253                          * to propagate. Instead, signal success. */
254                         g_clear_error (&error);
255                 }
256                 else
257                         _LOGW (info, "complete: request failed (%s)", error->message);
258         } else
259                 _LOGD (info, "complete: success");
260
261         _cb_info_complete_normal (info, error);
262 }
263
264 static NMFirewallManagerCallId
265 _start_request (NMFirewallManager *self,
266                 CBInfoOpsType ops_type,
267                 const char *iface,
268                 const char *zone,
269                 NMFirewallManagerAddRemoveCallback callback,
270                 gpointer user_data)
271 {
272         NMFirewallManagerPrivate *priv;
273         CBInfo *info;
274         const char *dbus_method;
275
276         g_return_val_if_fail (NM_IS_FIREWALL_MANAGER (self), NULL);
277         g_return_val_if_fail (iface && *iface, NULL);
278
279         priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
280
281         info = _cb_info_create (self, ops_type, iface, callback, user_data);
282
283         _LOGD (info, "firewall zone %s %s:%s%s%s%s",
284                _ops_type_to_string (info->ops_type),
285                iface,
286                NM_PRINT_FMT_QUOTED (zone, "\"", zone, "\"", "default"),
287                _cb_info_is_idle (info) ? " (not running, simulate success)" : "");
288
289         if (!_cb_info_is_idle (info)) {
290
291                 switch (ops_type) {
292                 case CB_INFO_OPS_ADD:
293                         dbus_method = "addInterface";
294                         break;
295                 case CB_INFO_OPS_CHANGE:
296                         dbus_method = "changeZone";
297                         break;
298                 case CB_INFO_OPS_REMOVE:
299                         dbus_method = "removeInterface";
300                         break;
301                 default:
302                         g_assert_not_reached ();
303                 }
304
305                 g_dbus_proxy_call (priv->proxy,
306                                    dbus_method,
307                                    g_variant_new ("(ss)", zone ? zone : "", iface),
308                                    G_DBUS_CALL_FLAGS_NONE, 10000,
309                                    info->dbus.cancellable,
310                                    _handle_dbus,
311                                    info);
312
313                 if (!info->callback) {
314                         /* if the user did not provide a callback, the call_id is useless.
315                          * Especially, the user cannot use the call-id to cancel the request,
316                          * because he cannot know whether the request is still pending.
317                          *
318                          * Hence, returning %NULL doesn't mean that the request could not be started
319                          * (the request will always be started). */
320                         return NULL;
321                 }
322         } else if (!info->callback) {
323                 /* if the user did not provide a callback and firewalld is not running,
324                  * there is no point in scheduling an idle-request to fake success. Just
325                  * return right away. */
326                 _LOGD (info, "complete: drop request simulating success");
327                 _cb_info_complete_normal (info, NULL);
328                 return NULL;
329         } else
330                 info->idle.id = g_idle_add (_handle_idle, info);
331
332         return info;
333 }
334
335 NMFirewallManagerCallId
336 nm_firewall_manager_add_or_change_zone (NMFirewallManager *self,
337                                         const char *iface,
338                                         const char *zone,
339                                         gboolean add, /* TRUE == add, FALSE == change */
340                                         NMFirewallManagerAddRemoveCallback callback,
341                                         gpointer user_data)
342 {
343         return _start_request (self,
344                                add ? CB_INFO_OPS_ADD : CB_INFO_OPS_CHANGE,
345                                iface,
346                                zone,
347                                callback,
348                                user_data);
349 }
350
351 NMFirewallManagerCallId
352 nm_firewall_manager_remove_from_zone (NMFirewallManager *self,
353                                       const char *iface,
354                                       const char *zone,
355                                       NMFirewallManagerAddRemoveCallback callback,
356                                       gpointer user_data)
357 {
358         return _start_request (self,
359                                CB_INFO_OPS_REMOVE,
360                                iface,
361                                zone,
362                                callback,
363                                user_data);
364 }
365
366 void
367 nm_firewall_manager_cancel_call (NMFirewallManagerCallId call)
368 {
369         NMFirewallManager *self;
370         NMFirewallManagerPrivate *priv;
371         CBInfo *info = call;
372         gs_free_error GError *error = NULL;
373
374         g_return_if_fail (info);
375         g_return_if_fail (NM_IS_FIREWALL_MANAGER (info->self));
376
377         self = info->self;
378         priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
379
380         if (!g_hash_table_remove (priv->pending_calls, info))
381                 g_return_if_reached ();
382
383         nm_utils_error_set_cancelled (&error, FALSE, "NMFirewallManager");
384
385         _LOGD (info, "complete: cancel (%s)", error->message);
386
387         _cb_info_callback (info, error);
388
389         if (_cb_info_is_idle (info)) {
390                 g_source_remove (info->idle.id);
391                 _cb_info_free (info);
392         } else {
393                 info->mode = CB_INFO_MODE_DBUS_COMPLETED;
394                 g_cancellable_cancel (info->dbus.cancellable);
395                 g_clear_object (&info->self);
396         }
397 }
398
399 /*******************************************************************/
400
401 static void
402 set_running (NMFirewallManager *self, gboolean now_running)
403 {
404         NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
405         gboolean old_running = priv->running;
406
407         priv->running = now_running;
408         if (old_running != priv->running)
409                 g_object_notify (G_OBJECT (self), NM_FIREWALL_MANAGER_AVAILABLE);
410 }
411
412 static void
413 name_owner_changed (GObject    *object,
414                     GParamSpec *pspec,
415                     gpointer    user_data)
416 {
417         NMFirewallManager *self = NM_FIREWALL_MANAGER (user_data);
418         gs_free char *owner = NULL;
419
420         owner = g_dbus_proxy_get_name_owner (G_DBUS_PROXY (object));
421         if (owner) {
422                 _LOGD (NULL, "firewall started");
423                 set_running (self, TRUE);
424                 g_signal_emit (self, signals[STARTED], 0);
425         } else {
426                 _LOGD (NULL, "firewall stopped");
427                 set_running (self, FALSE);
428         }
429 }
430
431 /*******************************************************************/
432
433 static void
434 nm_firewall_manager_init (NMFirewallManager * self)
435 {
436         NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
437
438         priv->pending_calls = g_hash_table_new (g_direct_hash, g_direct_equal);
439 }
440
441 static void
442 constructed (GObject *object)
443 {
444         NMFirewallManager *self = (NMFirewallManager *) object;
445         NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
446         gs_free char *owner = NULL;
447         gs_free_error GError *error = NULL;
448
449         G_OBJECT_CLASS (nm_firewall_manager_parent_class)->constructed (object);
450
451         priv->proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
452                                                      G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES |
453                                                          G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
454                                                      NULL,
455                                                      FIREWALL_DBUS_SERVICE,
456                                                      FIREWALL_DBUS_PATH,
457                                                      FIREWALL_DBUS_INTERFACE_ZONE,
458                                                      NULL, &error);
459         if (priv->proxy) {
460                 g_signal_connect (priv->proxy, "notify::g-name-owner",
461                                   G_CALLBACK (name_owner_changed), self);
462                 owner = g_dbus_proxy_get_name_owner (priv->proxy);
463                 priv->running = (owner != NULL);
464         } else {
465                 _LOGW (NULL, "could not connect to system D-Bus (%s)", error->message);
466         }
467
468         _LOGD (NULL, "firewall constructed (%srunning)", priv->running ? "" : "not");
469 }
470
471 static void
472 get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
473 {
474         switch (prop_id) {
475         case PROP_AVAILABLE:
476                 g_value_set_boolean (value, NM_FIREWALL_MANAGER_GET_PRIVATE (object)->running);
477                 break;
478         default:
479                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
480                 break;
481         }
482 }
483
484 static void
485 dispose (GObject *object)
486 {
487         NMFirewallManager *self = NM_FIREWALL_MANAGER (object);
488         NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE (self);
489
490         if (priv->pending_calls) {
491                 /* as every pending operation takes a reference to the manager,
492                  * we don't expect pending operations at this point. */
493                 g_assert (g_hash_table_size (priv->pending_calls) == 0);
494                 g_hash_table_unref (priv->pending_calls);
495                 priv->pending_calls = NULL;
496         }
497
498         g_clear_object (&priv->proxy);
499
500         /* Chain up to the parent class */
501         G_OBJECT_CLASS (nm_firewall_manager_parent_class)->dispose (object);
502 }
503
504 static void
505 nm_firewall_manager_class_init (NMFirewallManagerClass *klass)
506 {
507         GObjectClass *object_class = G_OBJECT_CLASS (klass);
508
509         g_type_class_add_private (object_class, sizeof (NMFirewallManagerPrivate));
510
511         object_class->constructed = constructed;
512         object_class->get_property = get_property;
513         object_class->dispose = dispose;
514
515         g_object_class_install_property
516             (object_class, PROP_AVAILABLE,
517              g_param_spec_boolean (NM_FIREWALL_MANAGER_AVAILABLE, "", "",
518                                    FALSE,
519                                    G_PARAM_READABLE |
520                                    G_PARAM_STATIC_STRINGS));
521
522         signals[STARTED] =
523             g_signal_new ("started",
524                           G_OBJECT_CLASS_TYPE (object_class),
525                           G_SIGNAL_RUN_FIRST,
526                           G_STRUCT_OFFSET (NMFirewallManagerClass, started),
527                           NULL, NULL,
528                           g_cclosure_marshal_VOID__VOID,
529                           G_TYPE_NONE, 0);
530
531 }
532