NetworkManager/src/core/nm-firewalld-manager.c
Thomas Haller 615221a99c format: reformat source tree with clang-format 13.0
We use clang-format for automatic formatting of our source files.
Since clang-format is actively maintained software, the actual
formatting depends on the used version of clang-format. That is
unfortunate and painful, but really unavoidable unless clang-format
would be strictly bug-compatible.

So the version that we must use is from the current Fedora release, which
is also tested by our gitlab-ci. Previously, we were using Fedora 34 with
clang-tools-extra-12.0.1-1.fc34.x86_64.

As Fedora 35 comes along, we need to update our formatting as Fedora 35
comes with version "13.0.0~rc1-1.fc35".
An alternative would be to freeze on version 12, but that has different
problems (like, it's cumbersome to rebuild clang 12 on Fedora 35 and it
would be cumbersome for our developers which are on Fedora 35 to use a
clang that they cannot easily install).

The (differently painful) solution is to reformat from time to time, as we
switch to a new Fedora (and thus clang) version.
Usually we would expect that such a reformatting brings minor changes.
But this time, the changes are huge. That is mentioned in the release
notes [1] as

  Makes PointerAligment: Right working with AlignConsecutiveDeclarations. (Fixes https://llvm.org/PR27353)

[1] https://releases.llvm.org/13.0.0/tools/clang/docs/ReleaseNotes.html#clang-format
2021-11-29 09:31:09 +00:00

669 lines
24 KiB
C

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2011 - 2015 Red Hat, Inc.
*/
#include "src/core/nm-default-daemon.h"
#include "nm-firewalld-manager.h"
#include "libnm-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 "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;
char *name_owner;
guint reloaded_id;
guint name_owner_changed_id;
bool dbus_inited : 1;
} NMFirewalldManagerPrivate;
struct _NMFirewalldManager {
GObject parent;
NMFirewalldManagerPrivate _priv;
};
struct _NMFirewalldManagerClass {
GObjectClass parent;
};
G_DEFINE_TYPE(NMFirewalldManager, nm_firewalld_manager, G_TYPE_OBJECT)
#define NM_FIREWALLD_MANAGER_GET_PRIVATE(self) \
_NM_GET_PRIVATE(self, NMFirewalldManager, NM_IS_FIREWALLD_MANAGER)
/*****************************************************************************/
NM_DEFINE_SINGLETON_GETTER(NMFirewalldManager, nm_firewalld_manager_get, NM_TYPE_FIREWALLD_MANAGER);
/*****************************************************************************/
typedef enum {
OPS_TYPE_ADD = 1,
OPS_TYPE_CHANGE,
OPS_TYPE_REMOVE,
} OpsType;
struct _NMFirewalldManagerCallId {
CList lst;
NMFirewalldManager *self;
char *iface;
NMFirewalldManagerAddRemoveCallback 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 "firewalld"
#define _NMLOG(level, call_id, ...) \
G_STMT_START \
{ \
if (nm_logging_enabled((level), (_NMLOG_DOMAIN))) { \
NMFirewalldManagerCallId *_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 void
_signal_emit_state_changed(NMFirewalldManager *self, NMFirewalldManagerStateChangedType signal_type)
{
g_signal_emit(self, signals[STATE_CHANGED], 0, (int) signal_type);
}
/*****************************************************************************/
static gboolean
_get_running(NMFirewalldManagerPrivate *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->name_owner || (priv->dbus_connection && !priv->dbus_inited);
}
gboolean
nm_firewalld_manager_get_running(NMFirewalldManager *self)
{
g_return_val_if_fail(NM_IS_FIREWALLD_MANAGER(self), FALSE);
return _get_running(NM_FIREWALLD_MANAGER_GET_PRIVATE(self));
}
/*****************************************************************************/
static NMFirewalldManagerCallId *
_cb_info_create(NMFirewalldManager *self,
OpsType ops_type,
const char *iface,
const char *zone,
NMFirewalldManagerAddRemoveCallback callback,
gpointer user_data)
{
NMFirewalldManagerPrivate *priv = NM_FIREWALLD_MANAGER_GET_PRIVATE(self);
NMFirewalldManagerCallId *call_id;
call_id = g_slice_new0(NMFirewalldManagerCallId);
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(NMFirewalldManagerCallId *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)
{
NMFirewalldManager *self;
NMFirewalldManagerCallId *call_id = user_data;
nm_assert(call_id);
nm_assert(NM_IS_FIREWALLD_MANAGER(call_id->self));
nm_assert(call_id->is_idle);
nm_assert(c_list_contains(&NM_FIREWALLD_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(NMFirewalldManager *self, NMFirewalldManagerCallId *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)
{
NMFirewalldManager *self;
NMFirewalldManagerCallId *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_FIREWALLD_MANAGER(call_id->self));
nm_assert(!call_id->is_idle);
nm_assert(c_list_contains(&NM_FIREWALLD_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(NMFirewalldManager *self, NMFirewalldManagerCallId *call_id)
{
NMFirewalldManagerPrivate *priv = NM_FIREWALLD_MANAGER_GET_PRIVATE(self);
const char *dbus_method = NULL;
GVariant *arg;
nm_assert(call_id);
nm_assert(priv->name_owner);
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 = "changeZoneOfInterface";
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,
priv->name_owner,
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 NMFirewalldManagerCallId *
_start_request(NMFirewalldManager *self,
OpsType ops_type,
const char *iface,
const char *zone,
NMFirewalldManagerAddRemoveCallback callback,
gpointer user_data)
{
NMFirewalldManagerPrivate *priv;
NMFirewalldManagerCallId *call_id;
g_return_val_if_fail(NM_IS_FIREWALLD_MANAGER(self), NULL);
g_return_val_if_fail(iface && *iface, NULL);
priv = NM_FIREWALLD_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->name_owner ? " (waiting to initialize)" : ""));
if (!call_id->is_idle) {
if (priv->name_owner)
_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;
}
NMFirewalldManagerCallId *
nm_firewalld_manager_add_or_change_zone(NMFirewalldManager *self,
const char *iface,
const char *zone,
gboolean add, /* TRUE == add, FALSE == change */
NMFirewalldManagerAddRemoveCallback callback,
gpointer user_data)
{
return _start_request(self,
add ? OPS_TYPE_ADD : OPS_TYPE_CHANGE,
iface,
zone,
callback,
user_data);
}
NMFirewalldManagerCallId *
nm_firewalld_manager_remove_from_zone(NMFirewalldManager *self,
const char *iface,
const char *zone,
NMFirewalldManagerAddRemoveCallback callback,
gpointer user_data)
{
return _start_request(self, OPS_TYPE_REMOVE, iface, zone, callback, user_data);
}
void
nm_firewalld_manager_cancel_call(NMFirewalldManagerCallId *call_id)
{
NMFirewalldManager *self;
NMFirewalldManagerPrivate *priv;
gs_free_error GError *error = NULL;
g_return_if_fail(call_id);
g_return_if_fail(NM_IS_FIREWALLD_MANAGER(call_id->self));
g_return_if_fail(!c_list_is_empty(&call_id->lst));
self = call_id->self;
priv = NM_FIREWALLD_MANAGER_GET_PRIVATE(self);
nm_assert(c_list_contains(&priv->pending_calls, &call_id->lst));
nm_utils_error_set_cancelled(&error, FALSE, "NMFirewalldManager");
_LOGD(call_id, "complete: cancel (%s)", error->message);
_cb_info_complete(call_id, error);
}
/*****************************************************************************/
static void
name_owner_changed(NMFirewalldManager *self, const char *owner)
{
_nm_unused gs_unref_object NMFirewalldManager *self_keep_alive = g_object_ref(self);
NMFirewalldManagerPrivate *priv = NM_FIREWALLD_MANAGER_GET_PRIVATE(self);
gboolean was_running;
gboolean now_running;
gboolean just_initied;
gboolean name_owner_changed;
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;
name_owner_changed = nm_strdup_reset(&priv->name_owner, owner);
now_running = _get_running(priv);
if (just_initied) {
NMFirewalldManagerCallId *call_id_safe;
NMFirewalldManagerCallId *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->name_owner) {
_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 (just_initied)
_signal_emit_state_changed(self, NM_FIREWALLD_MANAGER_STATE_CHANGED_TYPE_INITIALIZED);
else if (was_running != now_running || name_owner_changed)
_signal_emit_state_changed(self,
NM_FIREWALLD_MANAGER_STATE_CHANGED_TYPE_NAME_OWNER_CHANGED);
}
static void
reloaded_cb(GDBusConnection *connection,
const char *sender_name,
const char *object_path,
const char *interface_name,
const char *signal_name,
GVariant *parameters,
gpointer user_data)
{
NMFirewalldManager *self = user_data;
NMFirewalldManagerPrivate *priv = NM_FIREWALLD_MANAGER_GET_PRIVATE(self);
if (!nm_streq0(sender_name, priv->name_owner))
return;
_LOGT(NULL, "reloaded signal received");
_signal_emit_state_changed(self, NM_FIREWALLD_MANAGER_STATE_CHANGED_TYPE_RELOADED);
}
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)
{
NMFirewalldManager *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)
{
NMFirewalldManager *self;
NMFirewalldManagerPrivate *priv;
if (nm_utils_error_is_cancelled(error))
return;
self = user_data;
priv = NM_FIREWALLD_MANAGER_GET_PRIVATE(self);
g_clear_object(&priv->get_name_owner_cancellable);
name_owner_changed(self, name_owner);
}
/*****************************************************************************/
static void
nm_firewalld_manager_init(NMFirewalldManager *self)
{
NMFirewalldManagerPrivate *priv = NM_FIREWALLD_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->reloaded_id = g_dbus_connection_signal_subscribe(priv->dbus_connection,
FIREWALL_DBUS_SERVICE,
FIREWALL_DBUS_INTERFACE,
"Reloaded",
FIREWALL_DBUS_PATH,
NULL,
G_DBUS_SIGNAL_FLAGS_NONE,
reloaded_cb,
self,
NULL);
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)
{
NMFirewalldManager *self = NM_FIREWALLD_MANAGER(object);
NMFirewalldManagerPrivate *priv = NM_FIREWALLD_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->reloaded_id);
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_firewalld_manager_parent_class)->dispose(object);
g_clear_object(&priv->dbus_connection);
}
static void
nm_firewalld_manager_class_init(NMFirewalldManagerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
object_class->dispose = dispose;
signals[STATE_CHANGED] = g_signal_new(NM_FIREWALLD_MANAGER_STATE_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
g_cclosure_marshal_VOID__INT,
G_TYPE_NONE,
1,
G_TYPE_INT /* signal-type */);
}