NetworkManager/src/core/nm-firewall-manager.c
Thomas Haller ac1a9e03e4
all: move "src/" directory to "src/core/"
Currently "src/" mostly contains the source code of the daemon.
I say mostly, because that is not true, there are also the device,
settings, wwan, ppp plugins, the initrd generator, the pppd and dhcp
helper, and probably more.

Also we have source code under libnm-core/, libnm/, clients/, and
shared/ directories. That is all confusing.

We should have one "src" directory, that contains subdirectories. Those
subdirectories should contain individual parts (libraries or
applications), that possibly have dependencies on other subdirectories.
There should be a flat hierarchy of directories under src/, which
contains individual modules.

As the name "src/" is already taken, that prevents any sensible
restructuring of the code.

As a first step, move "src/" to "src/core/". This gives space to
reorganize the code better by moving individual components into "src/".

For inspiration, look at systemd's "src/" directory.

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/743
2021-02-04 09:45:55 +01:00

623 lines
22 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2011 - 2015 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-firewall-manager.h"
#include "nm-glib-aux/nm-dbus-aux.h"
#include "c-list/src/c-list.h"
#include "NetworkManagerUtils.h"
#include "nm-dbus-manager.h"
#define FIREWALL_DBUS_SERVICE "org.fedoraproject.FirewallD1"
#define FIREWALL_DBUS_PATH "/org/fedoraproject/FirewallD1"
#define FIREWALL_DBUS_INTERFACE_ZONE "org.fedoraproject.FirewallD1.zone"
/*****************************************************************************/
enum { STATE_CHANGED, LAST_SIGNAL };
static guint signals[LAST_SIGNAL] = {0};
typedef struct {
GDBusConnection *dbus_connection;
GCancellable *get_name_owner_cancellable;
CList pending_calls;
guint name_owner_changed_id;
bool dbus_inited : 1;
bool running : 1;
} NMFirewallManagerPrivate;
struct _NMFirewallManager {
GObject parent;
NMFirewallManagerPrivate _priv;
};
struct _NMFirewallManagerClass {
GObjectClass parent;
};
G_DEFINE_TYPE(NMFirewallManager, nm_firewall_manager, G_TYPE_OBJECT)
#define NM_FIREWALL_MANAGER_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMFirewallManager, NM_IS_FIREWALL_MANAGER)
/*****************************************************************************/
NM_DEFINE_SINGLETON_GETTER(NMFirewallManager, nm_firewall_manager_get, NM_TYPE_FIREWALL_MANAGER);
/*****************************************************************************/
typedef enum {
OPS_TYPE_ADD = 1,
OPS_TYPE_CHANGE,
OPS_TYPE_REMOVE,
} OpsType;
struct _NMFirewallManagerCallId {
CList lst;
NMFirewallManager *self;
char *iface;
NMFirewallManagerAddRemoveCallback callback;
gpointer user_data;
union {
struct {
GCancellable *cancellable;
GVariant * arg;
} dbus;
struct {
guint id;
} idle;
};
OpsType ops_type;
bool is_idle : 1;
};
/*****************************************************************************/
static const char *
_ops_type_to_string(OpsType ops_type)
{
switch (ops_type) {
case OPS_TYPE_ADD:
return "add";
case OPS_TYPE_REMOVE:
return "remove";
case OPS_TYPE_CHANGE:
return "change";
}
nm_assert_not_reached();
return NULL;
}
#define _NMLOG_DOMAIN LOGD_FIREWALL
#define _NMLOG_PREFIX_NAME "firewall"
#define _NMLOG(level, call_id, ...) \
G_STMT_START \
{ \
if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \
NMFirewallManagerCallId *_call_id = (call_id); \
char _prefix_name[30]; \
char _prefix_info[100]; \
\
_nm_log((level), \
(_NMLOG_DOMAIN), \
0, \
NULL, \
NULL, \
"%s: %s" _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
(self) != singleton_instance ? ({ \
g_snprintf(_prefix_name, \
sizeof(_prefix_name), \
"%s[" NM_HASH_OBFUSCATE_PTR_FMT "]", \
""_NMLOG_PREFIX_NAME, \
NM_HASH_OBFUSCATE_PTR(self)); \
_prefix_name; \
}) \
: _NMLOG_PREFIX_NAME, \
_call_id ? ({ \
g_snprintf(_prefix_info, \
sizeof(_prefix_info), \
"[" NM_HASH_OBFUSCATE_PTR_FMT ",%s%s:%s%s%s]: ", \
NM_HASH_OBFUSCATE_PTR(_call_id), \
_ops_type_to_string(_call_id->ops_type), \
_call_id->is_idle ? "*" : "", \
NM_PRINT_FMT_QUOTE_STRING(_call_id->iface)); \
_prefix_info; \
}) \
: "" _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
/*****************************************************************************/
static gboolean
_get_running(NMFirewallManagerPrivate *priv)
{
/* when starting, we need to asynchronously check whether there is
* a name owner. During that time we optimistically assume that the
* service is indeed running. That is the time when we queue the
* requests, and they will be started once the get-name-owner call
* returns. */
return priv->running || (priv->dbus_connection && !priv->dbus_inited);
}
gboolean
nm_firewall_manager_get_running(NMFirewallManager *self)
{
g_return_val_if_fail(NM_IS_FIREWALL_MANAGER(self), FALSE);
return _get_running(NM_FIREWALL_MANAGER_GET_PRIVATE(self));
}
/*****************************************************************************/
static NMFirewallManagerCallId *
_cb_info_create(NMFirewallManager * self,
OpsType ops_type,
const char * iface,
const char * zone,
NMFirewallManagerAddRemoveCallback callback,
gpointer user_data)
{
NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
NMFirewallManagerCallId * call_id;
call_id = g_slice_new0(NMFirewallManagerCallId);
call_id->self = g_object_ref(self);
call_id->ops_type = ops_type;
call_id->iface = g_strdup(iface);
call_id->callback = callback;
call_id->user_data = user_data;
if (_get_running(priv)) {
call_id->is_idle = FALSE;
call_id->dbus.arg = g_variant_new("(ss)", zone ?: "", iface);
} else
call_id->is_idle = TRUE;
c_list_link_tail(&priv->pending_calls, &call_id->lst);
return call_id;
}
static void
_cb_info_complete(NMFirewallManagerCallId *call_id, GError *error)
{
c_list_unlink(&call_id->lst);
if (call_id->callback)
call_id->callback(call_id->self, call_id, error, call_id->user_data);
if (call_id->is_idle)
nm_clear_g_source(&call_id->idle.id);
else {
nm_g_variant_unref(call_id->dbus.arg);
nm_clear_g_cancellable(&call_id->dbus.cancellable);
}
g_free(call_id->iface);
g_object_unref(call_id->self);
nm_g_slice_free(call_id);
}
static gboolean
_handle_idle_cb(gpointer user_data)
{
NMFirewallManager * self;
NMFirewallManagerCallId *call_id = user_data;
nm_assert(call_id);
nm_assert(NM_IS_FIREWALL_MANAGER(call_id->self));
nm_assert(call_id->is_idle);
nm_assert(c_list_contains(&NM_FIREWALL_MANAGER_GET_PRIVATE(call_id->self)->pending_calls,
&call_id->lst));
self = call_id->self;
_LOGD(call_id, "complete: fake success");
call_id->idle.id = 0;
_cb_info_complete(call_id, NULL);
return G_SOURCE_REMOVE;
}
static gboolean
_handle_idle_start(NMFirewallManager *self, NMFirewallManagerCallId *call_id)
{
if (!call_id->callback) {
/* if the user did not provide a callback and firewalld is not running,
* there is no point in scheduling an idle-request to fake success. Just
* return right away. */
_LOGD(call_id, "complete: drop request simulating success");
_cb_info_complete(call_id, NULL);
return FALSE;
}
call_id->idle.id = g_idle_add(_handle_idle_cb, call_id);
return TRUE;
}
static void
_handle_dbus_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMFirewallManager * self;
NMFirewallManagerCallId *call_id;
gs_free_error GError *error = NULL;
gs_unref_variant GVariant *ret = NULL;
ret = g_dbus_connection_call_finish(G_DBUS_CONNECTION(source), result, &error);
if (!ret && nm_utils_error_is_cancelled(error))
return;
call_id = user_data;
nm_assert(call_id);
nm_assert(NM_IS_FIREWALL_MANAGER(call_id->self));
nm_assert(!call_id->is_idle);
nm_assert(c_list_contains(&NM_FIREWALL_MANAGER_GET_PRIVATE(call_id->self)->pending_calls,
&call_id->lst));
self = call_id->self;
if (error) {
const char *non_error = NULL;
g_dbus_error_strip_remote_error(error);
switch (call_id->ops_type) {
case OPS_TYPE_ADD:
case OPS_TYPE_CHANGE:
non_error = "ZONE_ALREADY_SET";
break;
case OPS_TYPE_REMOVE:
non_error = "UNKNOWN_INTERFACE";
break;
}
if (error->message && non_error && g_str_has_prefix(error->message, non_error)
&& NM_IN_SET(error->message[strlen(non_error)], '\0', ':')) {
_LOGD(call_id, "complete: request failed with a non-error (%s)", error->message);
/* The operation failed with an error reason that we don't want
* to propagate. Instead, signal success. */
g_clear_error(&error);
} else
_LOGW(call_id, "complete: request failed (%s)", error->message);
} else
_LOGD(call_id, "complete: success");
g_clear_object(&call_id->dbus.cancellable);
_cb_info_complete(call_id, error);
}
static void
_handle_dbus_start(NMFirewallManager *self, NMFirewallManagerCallId *call_id)
{
NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
const char * dbus_method = NULL;
GVariant * arg;
nm_assert(call_id);
nm_assert(priv->running);
nm_assert(!call_id->is_idle);
nm_assert(c_list_contains(&priv->pending_calls, &call_id->lst));
switch (call_id->ops_type) {
case OPS_TYPE_ADD:
dbus_method = "addInterface";
break;
case OPS_TYPE_CHANGE:
dbus_method = "changeZone";
break;
case OPS_TYPE_REMOVE:
dbus_method = "removeInterface";
break;
}
nm_assert(dbus_method);
arg = g_steal_pointer(&call_id->dbus.arg);
nm_assert(arg && g_variant_is_floating(arg));
nm_assert(!call_id->dbus.cancellable);
call_id->dbus.cancellable = g_cancellable_new();
g_dbus_connection_call(priv->dbus_connection,
FIREWALL_DBUS_SERVICE,
FIREWALL_DBUS_PATH,
FIREWALL_DBUS_INTERFACE_ZONE,
dbus_method,
arg,
NULL,
G_DBUS_CALL_FLAGS_NONE,
10000,
call_id->dbus.cancellable,
_handle_dbus_cb,
call_id);
}
static NMFirewallManagerCallId *
_start_request(NMFirewallManager * self,
OpsType ops_type,
const char * iface,
const char * zone,
NMFirewallManagerAddRemoveCallback callback,
gpointer user_data)
{
NMFirewallManagerPrivate *priv;
NMFirewallManagerCallId * call_id;
g_return_val_if_fail(NM_IS_FIREWALL_MANAGER(self), NULL);
g_return_val_if_fail(iface && *iface, NULL);
priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
call_id = _cb_info_create(self, ops_type, iface, zone, callback, user_data);
_LOGD(call_id,
"firewall zone %s %s:%s%s%s%s",
_ops_type_to_string(call_id->ops_type),
iface,
NM_PRINT_FMT_QUOTED(zone, "\"", zone, "\"", "default"),
call_id->is_idle ? " (not running, simulate success)"
: (!priv->running ? " (waiting to initialize)" : ""));
if (!call_id->is_idle) {
if (priv->running)
_handle_dbus_start(self, call_id);
if (!call_id->callback) {
/* if the user did not provide a callback, the call_id is useless.
* Especially, the user cannot use the call-id to cancel the request,
* because he cannot know whether the request is still pending.
*
* Hence, returning %NULL doesn't mean that the request could not be started
* (this function never fails and always starts a request). */
return NULL;
}
} else {
if (!_handle_idle_start(self, call_id)) {
/* if the user did not provide a callback and firewalld is not running,
* there is no point in scheduling an idle-request to fake success. Just
* return right away. */
return NULL;
}
}
return call_id;
}
NMFirewallManagerCallId *
nm_firewall_manager_add_or_change_zone(NMFirewallManager *self,
const char * iface,
const char * zone,
gboolean add, /* TRUE == add, FALSE == change */
NMFirewallManagerAddRemoveCallback callback,
gpointer user_data)
{
return _start_request(self,
add ? OPS_TYPE_ADD : OPS_TYPE_CHANGE,
iface,
zone,
callback,
user_data);
}
NMFirewallManagerCallId *
nm_firewall_manager_remove_from_zone(NMFirewallManager * self,
const char * iface,
const char * zone,
NMFirewallManagerAddRemoveCallback callback,
gpointer user_data)
{
return _start_request(self, OPS_TYPE_REMOVE, iface, zone, callback, user_data);
}
void
nm_firewall_manager_cancel_call(NMFirewallManagerCallId *call_id)
{
NMFirewallManager * self;
NMFirewallManagerPrivate *priv;
gs_free_error GError *error = NULL;
g_return_if_fail(call_id);
g_return_if_fail(NM_IS_FIREWALL_MANAGER(call_id->self));
g_return_if_fail(!c_list_is_empty(&call_id->lst));
self = call_id->self;
priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
nm_assert(c_list_contains(&priv->pending_calls, &call_id->lst));
nm_utils_error_set_cancelled(&error, FALSE, "NMFirewallManager");
_LOGD(call_id, "complete: cancel (%s)", error->message);
_cb_info_complete(call_id, error);
}
/*****************************************************************************/
static void
name_owner_changed(NMFirewallManager *self, const char *owner)
{
_nm_unused gs_unref_object NMFirewallManager *self_keep_alive = g_object_ref(self);
NMFirewallManagerPrivate * priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
gboolean was_running;
gboolean now_running;
gboolean just_initied;
owner = nm_str_not_empty(owner);
if (!owner)
_LOGT(NULL, "D-Bus name for firewalld has no owner (firewall stopped)");
else
_LOGT(NULL, "D-Bus name for firewalld has owner %s (firewall started)", owner);
was_running = _get_running(priv);
just_initied = !priv->dbus_inited;
priv->dbus_inited = TRUE;
priv->running = !!owner;
now_running = _get_running(priv);
if (just_initied) {
NMFirewallManagerCallId *call_id_safe;
NMFirewallManagerCallId *call_id;
/* We kick of the requests that we have pending. Note that this is
* entirely asynchronous and also we don't invoke any callbacks for
* the user.
* Even _handle_idle_start() just schedules an idle handler. That is,
* because we don't want to callback to the user before emitting the
* DISCONNECTED signal below. Also, emitting callbacks means the user
* can call back to modify the list of pending-calls and we'd have
* to handle reentrancy. */
c_list_for_each_entry_safe (call_id, call_id_safe, &priv->pending_calls, lst) {
nm_assert(!call_id->is_idle);
nm_assert(call_id->dbus.arg);
if (priv->running) {
_LOGD(call_id, "initalizing: make D-Bus call");
_handle_dbus_start(self, call_id);
} else {
/* we don't want to invoke callbacks to the user right away. That is because
* the user might schedule/cancel more calls, which messes up the order.
*
* Instead, convert the pending calls to idle requests... */
nm_clear_pointer(&call_id->dbus.arg, g_variant_unref);
call_id->is_idle = TRUE;
_LOGD(call_id, "initializing: fake success on idle");
_handle_idle_start(self, call_id);
}
}
}
if (was_running != now_running)
g_signal_emit(self, signals[STATE_CHANGED], 0, FALSE);
}
static void
name_owner_changed_cb(GDBusConnection *connection,
const char * sender_name,
const char * object_path,
const char * interface_name,
const char * signal_name,
GVariant * parameters,
gpointer user_data)
{
NMFirewallManager *self = user_data;
const char * new_owner;
if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)")))
return;
g_variant_get(parameters, "(&s&s&s)", NULL, NULL, &new_owner);
name_owner_changed(self, new_owner);
}
static void
get_name_owner_cb(const char *name_owner, GError *error, gpointer user_data)
{
NMFirewallManager * self;
NMFirewallManagerPrivate *priv;
if (!name_owner && g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
self = user_data;
priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
g_clear_object(&priv->get_name_owner_cancellable);
name_owner_changed(self, name_owner);
}
/*****************************************************************************/
static void
nm_firewall_manager_init(NMFirewallManager *self)
{
NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
c_list_init(&priv->pending_calls);
priv->dbus_connection = nm_g_object_ref(NM_MAIN_DBUS_CONNECTION_GET);
if (!priv->dbus_connection) {
_LOGD(NULL, "no D-Bus connection");
return;
}
priv->name_owner_changed_id =
nm_dbus_connection_signal_subscribe_name_owner_changed(priv->dbus_connection,
FIREWALL_DBUS_SERVICE,
name_owner_changed_cb,
self,
NULL);
priv->get_name_owner_cancellable = g_cancellable_new();
nm_dbus_connection_call_get_name_owner(priv->dbus_connection,
FIREWALL_DBUS_SERVICE,
-1,
priv->get_name_owner_cancellable,
get_name_owner_cb,
self);
}
static void
dispose(GObject *object)
{
NMFirewallManager * self = NM_FIREWALL_MANAGER(object);
NMFirewallManagerPrivate *priv = NM_FIREWALL_MANAGER_GET_PRIVATE(self);
/* as every pending operation takes a reference to the manager,
* we don't expect pending operations at this point. */
nm_assert(c_list_is_empty(&priv->pending_calls));
nm_clear_g_dbus_connection_signal(priv->dbus_connection, &priv->name_owner_changed_id);
nm_clear_g_cancellable(&priv->get_name_owner_cancellable);
G_OBJECT_CLASS(nm_firewall_manager_parent_class)->dispose(object);
g_clear_object(&priv->dbus_connection);
}
static void
nm_firewall_manager_class_init(NMFirewallManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->dispose = dispose;
signals[STATE_CHANGED] = g_signal_new(NM_FIREWALL_MANAGER_STATE_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE,
1,
G_TYPE_BOOLEAN /* initialized_now */);
}