Commit 10b56298 authored by Jussi Laakkonen's avatar Jussi Laakkonen

[connman] D-Bus method to reload firewall configurations. JB#44071

This commit adds a D-Bus method "Reload" to net.connman.Firewall
interface using path "/". With this method call firewall.c is requested
to load all new configurations from CONFIGDIR/firewall.d/. Access to the
method is granted for root and privileged.

The config files must have a firewall.conf suffix and if the file is read
properly the rules will be taken into use immediatedly. This is done for
all connected services also, which get the new rules added into their
firewall and enabled in iptables. If a service is not connected
(firewall is not enabled) the rules are just added to the end.

No sorting of rules is done yet. The rules are read in firewall
configuration file order (alphabetical) only when connman is (re)started.

This also contains a change to read each firewall configuration file
into a sorted list. This list is first used to check if the
configuration file is already used or not. If configuration file is
already read, it will not be re-read.
parent 279e86b2
......@@ -51,6 +51,8 @@ struct connman_access_tech_policy;
struct connman_access_tech_policy_impl;
struct connman_access_manager_policy;
struct connman_access_manager_policy_impl;
struct connman_access_firewall_policy;
struct connman_access_firewall_policy_impl;
struct connman_access_driver {
const char *name;
......@@ -90,6 +92,16 @@ struct connman_access_driver {
(const struct connman_access_tech_policy_impl *policy,
const char *name, const char *sender,
enum connman_access default_access);
/* Firewall */
struct connman_access_firewall_policy_impl *(*firewall_policy_create)
(const char *spec);
void (*firewall_policy_free)
(struct connman_access_firewall_policy_impl *policy);
enum connman_access (*firewall_manage)
(const struct connman_access_firewall_policy_impl *policy,
const char *name, const char *sender,
enum connman_access default_access);
};
int connman_access_driver_register(const struct connman_access_driver *d);
......
......@@ -48,6 +48,9 @@ extern "C" {
#define CONNMAN_NOTIFICATION_INTERFACE CONNMAN_SERVICE ".Notification"
#define CONNMAN_PEER_INTERFACE CONNMAN_SERVICE ".Peer"
#define CONNMAN_FIREWALL_INTERFACE CONNMAN_SERVICE ".Firewall"
#define CONNMAN_FIREWALL_PATH "/"
#define CONNMAN_PRIVILEGE_MODIFY 1
#define CONNMAN_PRIVILEGE_SECRET 2
......
......@@ -42,10 +42,18 @@ struct connman_access_tech_policy_impl {
DAPolicy *impl;
};
struct connman_access_firewall_policy_impl {
DAPolicy *impl;
};
enum sailfish_tech_access_action {
TECH_ACCESS_SET_PROPERTY = 1
};
enum sailfish_firewall_access_action {
FIREWALL_ACCESS_RELOAD = 1
};
#define CONNMAN_BUS DA_BUS_SYSTEM
#define DRIVER_NAME "sailfish"
......@@ -113,6 +121,15 @@ static const DA_ACTION tech_policy_actions [] = {
{ NULL }
};
static const char *firewall_policy_default =
DA_POLICY_VERSION ";"
"Reload(*)=deny;"
"group(privileged)=allow";
static const DA_ACTION firewall_policy_actions [] = {
{ "Reload", FIREWALL_ACCESS_RELOAD, 1 },
{ NULL }
};
static void sailfish_access_service_policy_free
(struct connman_access_service_policy_impl *p)
{
......@@ -306,6 +323,53 @@ static enum connman_access sailfish_access_tech_set_property
default_access) : CONNMAN_ACCESS_DENY;
}
static struct connman_access_firewall_policy_impl *
sailfish_access_firewall_policy_create(const char *spec)
{
DAPolicy *impl;
if (!spec || !spec[0]) {
/* Empty policy = use default */
spec = firewall_policy_default;
}
/* Parse the policy string */
impl = da_policy_new_full(spec, firewall_policy_actions);
if (impl) {
/* String is usable */
struct connman_access_firewall_policy_impl *p =
g_slice_new0(
struct connman_access_firewall_policy_impl);
p->impl = impl;
return p;
} else {
DBG("invalid spec \"%s\"", spec);
return NULL;
}
}
static void sailfish_access_firewall_policy_free
(struct connman_access_firewall_policy_impl *p)
{
da_policy_unref(p->impl);
g_slice_free(struct connman_access_firewall_policy_impl, p);
}
static enum connman_access sailfish_access_firewall_manage
(const struct connman_access_firewall_policy_impl *policy,
const char *name, const char *sender,
enum connman_access default_access)
{
/* Don't unref this one: */
DAPeer* peer = da_peer_get(CONNMAN_BUS, sender);
/* Reject the access if the peer is gone */
return peer ? (enum connman_access)da_policy_check(policy->impl,
&peer->cred, FIREWALL_ACCESS_RELOAD, name,
(DA_ACCESS)default_access) : CONNMAN_ACCESS_DENY;
}
static const struct connman_access_driver sailfish_connman_access_driver = {
.name = DRIVER_NAME,
.default_service_policy = service_policy_default,
......@@ -318,7 +382,10 @@ static const struct connman_access_driver sailfish_connman_access_driver = {
.manager_policy_check = sailfish_access_manager_policy_check,
.tech_policy_create = sailfish_access_tech_policy_create,
.tech_policy_free = sailfish_access_tech_policy_free,
.tech_set_property = sailfish_access_tech_set_property
.tech_set_property = sailfish_access_tech_set_property,
.firewall_policy_create = sailfish_access_firewall_policy_create,
.firewall_policy_free = sailfish_access_firewall_policy_free,
.firewall_manage = sailfish_access_firewall_manage
};
static int sailfish_access_init()
......
......@@ -34,6 +34,11 @@ struct connman_access_tech_policy {
const struct connman_access_driver *driver;
};
struct connman_access_firewall_policy {
struct connman_access_firewall_policy_impl *impl;
const struct connman_access_driver *driver;
};
#define DRIVER_NAME_SEPARATOR ':'
#define DRIVER_NAME_SEPARATOR_STR ":"
......@@ -333,6 +338,52 @@ enum connman_access __connman_access_tech_set_property
return default_access;
}
/* Firewall */
struct connman_access_firewall_policy *__connman_access_firewall_policy_create
(const char *spec)
{
struct connman_access_firewall_policy *p = NULL;
const struct connman_access_driver *driver =
access_get_driver(spec, &spec);
if (driver && driver->firewall_policy_create) {
struct connman_access_firewall_policy_impl *impl =
driver->firewall_policy_create(spec);
if (impl) {
p = g_slice_new(struct connman_access_firewall_policy);
p->impl = impl;
p->driver = driver;
}
}
return p;
}
void __connman_access_firewall_policy_free(
struct connman_access_firewall_policy *p)
{
if (p) {
if (p->driver->firewall_policy_free)
p->driver->firewall_policy_free(p->impl);
g_slice_free(struct connman_access_firewall_policy, p);
}
}
enum connman_access __connman_access_firewall_manage
(const struct connman_access_firewall_policy *p,
const char *name, const char *sender,
enum connman_access default_access)
{
if (p && p->driver->firewall_manage)
return p->driver->firewall_manage(p->impl, name, sender,
default_access);
return default_access;
}
/*
* Local Variables:
* mode: C
......
......@@ -1154,6 +1154,15 @@ enum connman_access __connman_access_tech_set_property
const char *name, const char *sender,
enum connman_access default_access);
struct connman_access_firewall_policy *__connman_access_firewall_policy_create
(const char *spec);
void __connman_access_firewall_policy_free
(struct connman_access_firewall_policy *policy);
enum connman_access __connman_access_firewall_manage
(const struct connman_access_firewall_policy *policy,
const char *name, const char *sender,
enum connman_access default_access);
int __connman_util_get_random(uint64_t *val);
unsigned int __connman_util_random_delay_ms(unsigned int secs);
int __connman_util_init(void);
......
......@@ -30,6 +30,8 @@
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <gdbus.h>
#include "connman.h"
#define CHAIN_PREFIX "connman-"
......@@ -96,6 +98,9 @@ static struct firewall_context **dynamic_rules = NULL;
/* Tethering rules are a special case */
static struct firewall_context *tethering_firewall = NULL;
/* Configuration files that are read */
static GList *configuration_files = NULL;
static const char *supported_chains[] = {
[NF_IP_PRE_ROUTING] = NULL,
[NF_IP_LOCAL_IN] = "IPv4.INPUT.RULES",
......@@ -1947,14 +1952,6 @@ static int add_rules_from_group(GKeyFile *config, const char *group,
DBG("group %s chain %s error: %s",
group, chain_name,
error->message);
} else {
/*
* Error with rules set as NULL = no such key
* exists in the group specified.
*/
DBG("type %d no rules found for group %s "
"chain %s", types[i],
group, chain_name);
}
g_clear_error(&error);
......@@ -2449,7 +2446,6 @@ out:
static int init_all_dynamic_firewall_rules(void)
{
GList *filenames = NULL;
GList *iter;
GError *error = NULL;
GDir *dir;
......@@ -2483,15 +2479,20 @@ static int init_all_dynamic_firewall_rules(void)
continue;
/*
* Filename is used for sorting the list, no need to
* allocate a new one.
* Prepend read files into list of configuration
* files to be used in checks when new configurations
* are added to avoid unnecessary reads of already read
* configurations. Sort list after all are added.
*/
filenames = g_list_insert_sorted(filenames,
(char*)filename,
(GCompareFunc)g_strcmp0);
configuration_files = g_list_prepend(
configuration_files,
g_strdup(filename));
}
for (iter = filenames; iter ; iter = iter->next) {
configuration_files = g_list_sort(configuration_files,
(GCompareFunc)g_strcmp0);
for (iter = configuration_files; iter ; iter = iter->next) {
filename = iter->data;
filepath = g_strconcat(FIREWALLCONFIGDIR, filename,
......@@ -2507,8 +2508,6 @@ static int init_all_dynamic_firewall_rules(void)
g_free(filepath);
}
/* Strings in list are owned by GLib, don't free them. */
g_list_free(filenames);
g_dir_close(dir);
} else {
DBG("no config dir %s", FIREWALLCONFIGDIR);
......@@ -2674,6 +2673,200 @@ static void firewall_failsafe(const char *chain_name, void *user_data)
chain_name, err);
}
static int copy_new_dynamic_rules(struct firewall_context *dyn_ctx,
struct firewall_context *srv_ctx, char* ifname)
{
GList *srv_list;
GList *dyn_list;
struct fw_rule *dyn_rule;
struct fw_rule *srv_rule;
struct fw_rule *new_rule;
int err;
/* Go over dynamic rules for this type */
for (dyn_list = dyn_ctx->rules; dyn_list; dyn_list = dyn_list->next) {
dyn_rule = dyn_list->data;
bool found = false;
for (srv_list = srv_ctx->rules; srv_list;
srv_list = srv_list->next) {
srv_rule = srv_list->data;
/*
* If rule_spec is identical the do not add dynamic
* rule into this service firewall, it is already added.
*/
if (!g_strcmp0(dyn_rule->rule_spec,
srv_rule->rule_spec)) {
found = true;
break;
}
}
if (found)
continue;
new_rule = copy_fw_rule(dyn_rule, ifname);
srv_ctx->rules = g_list_append(srv_ctx->rules, new_rule);
if (srv_ctx->enabled) {
err = firewall_enable_rule(new_rule);
if (err)
DBG("new rule not enabled %d", err);
}
}
return 0;
}
static int firewall_reload_configurations()
{
GError *error = NULL;
GDir *dir;
GHashTableIter iter;
gpointer key, value;
struct connman_service *service;
enum connman_service_type type;
struct firewall_context *ctx;
const char *filename;
char *ifname;
char *filepath;
bool new_configuration_files = false;
int err = 0;
/* Nothing to read */
if (!g_file_test(FIREWALLCONFIGDIR, G_FILE_TEST_IS_DIR))
return 0;
dir = g_dir_open(FIREWALLCONFIGDIR, 0, &error);
if (!dir) {
if (error) {
DBG("cannot open dir, error: %s", error->message);
g_clear_error(&error);
}
/* Ignore dir open error in reload */
return 0;
}
DBG("read configs from %s", FIREWALLCONFIGDIR);
while ((filename = g_dir_read_name(dir))) {
/* Read configs that have firewall.conf suffix */
if (!g_str_has_suffix(filename, FIREWALLFILE))
continue;
/* If config file is already read */
if (g_list_find_custom(configuration_files, filename,
(GCompareFunc)g_strcmp0))
continue;
filepath = g_strconcat(FIREWALLCONFIGDIR, filename, NULL);
if (g_file_test(filepath, G_FILE_TEST_IS_REGULAR)) {
err = init_dynamic_firewall_rules(filepath);
if (!err) {
DBG("new configuration %s loaded", filepath);
configuration_files = g_list_insert_sorted(
configuration_files,
g_strdup(filename),
(GCompareFunc)g_strcmp0);
new_configuration_files = true;
}
}
g_free(filepath);
}
g_dir_close(dir);
if (!new_configuration_files) {
DBG("no new configuration was found");
return 0;
}
/* Apply general firewall rules that were added */
__connman_firewall_enable_rule(general_firewall->ctx, FW_ALL_RULES);
g_hash_table_iter_init(&iter, current_dynamic_rules);
/*
* Go through all service specific firewalls and add new rules
* for each.
*/
while (g_hash_table_iter_next(&iter, &key, &value)) {
service = connman_service_lookup_from_identifier(key);
if (!service)
continue;
type = connman_service_get_type(service);
ifname = connman_service_get_interface(service);
if (!has_dynamic_rules_set(type))
continue;
ctx = value;
copy_new_dynamic_rules(dynamic_rules[type], ctx, ifname);
g_free(ifname);
}
return 0;
}
static struct connman_access_firewall_policy *firewall_access_policy = NULL;
static struct connman_access_firewall_policy *get_firewall_access_policy()
{
if (!firewall_access_policy) {
/* Use the default policy */
firewall_access_policy =
__connman_access_firewall_policy_create(NULL);
}
return firewall_access_policy;
}
static DBusMessage *reload(DBusConnection *conn,
DBusMessage *msg, void *data)
{
int err;
DBG("conn %p", conn);
if (__connman_access_firewall_manage(get_firewall_access_policy(),
"Reload", dbus_message_get_sender(msg),
CONNMAN_ACCESS_ALLOW) != CONNMAN_ACCESS_ALLOW) {
DBG("%s is not allowed to reload firewall configurations",
dbus_message_get_sender(msg));
return __connman_error_permission_denied(msg);
}
err = firewall_reload_configurations();
/* TODO proper error reporting if necessary/sensible */
if (err)
return __connman_error_failed(msg, err);
return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
}
static DBusConnection *connection = NULL;
static const GDBusMethodTable firewall_methods[] = {
{ GDBUS_ASYNC_METHOD("Reload", NULL, NULL, reload) },
{ },
};
static struct connman_notifier firewall_notifier = {
.name = "firewall",
.service_state_changed = service_state_changed,
......@@ -2698,6 +2891,20 @@ int __connman_firewall_init(void)
DBG("cannot register notifier, dynamic rules disabled");
cleanup_dynamic_firewall_rules();
}
connection = connman_dbus_get_connection();
if (!g_dbus_register_interface(connection,
CONNMAN_FIREWALL_PATH,
CONNMAN_FIREWALL_INTERFACE,
firewall_methods, NULL, NULL, NULL,
NULL)) {
DBG("cannot register dbus, new firewall configuration "
"cannot be installed runtime");
dbus_connection_unref(connection);
connection = NULL;
}
} else {
DBG("dynamic rules disabled, policy ACCEPT set for all chains");
connman_error("firewall initialization error, reset iptables");
......@@ -2748,8 +2955,23 @@ void __connman_firewall_cleanup(void)
{
DBG("");
if (connection) {
if (!g_dbus_unregister_interface(connection,
CONNMAN_FIREWALL_PATH,
CONNMAN_FIREWALL_INTERFACE))
DBG("dbus unregister failed");
dbus_connection_unref(connection);
}
__connman_access_firewall_policy_free(firewall_access_policy);
firewall_access_policy = NULL;
cleanup_dynamic_firewall_rules();
g_list_free_full(configuration_files, g_free);
configuration_files = NULL;
g_slist_free_full(managed_tables, cleanup_managed_table);
managed_tables = NULL;
}
......@@ -155,6 +155,47 @@ void connman_technology_tethering_notify(struct connman_technology *technology,
return;
}
DBusMessage *__connman_error_invalid_arguments(DBusMessage *msg)
{
return NULL;
}
DBusMessage *__connman_error_permission_denied(DBusMessage *msg)
{
return NULL;
}
DBusMessage *__connman_error_failed(DBusMessage *msg, int errnum)
{
return NULL;
}
DBusConnection *connman_dbus_get_connection(void)
{
return NULL;
}
struct connman_access_firewall_policy *__connman_access_firewall_policy_create
(const char *spec)
{
return NULL;
}
void __connman_access_firewall_policy_free
(struct connman_access_firewall_policy *policy)
{
return;
}
enum connman_access __connman_access_firewall_manage
(const struct connman_access_firewall_policy *policy,
const char *name, const char *sender,
enum connman_access default_access)
{
return 0;
}
static bool assert_rule(int type, const char *table_name, const char *rule)
{
char *cmd, *output, **lines;
......
......@@ -32,6 +32,10 @@ struct connman_access_tech_policy_impl {
int unused;
};
struct connman_access_firewall_policy_impl {
int unused;
};
static const struct connman_access_driver test_inval;
static struct connman_access_service_policy_impl *test_service_policy_create
......@@ -82,6 +86,18 @@ static void test_tech_policy_free
g_free(policy);
}
static struct connman_access_firewall_policy_impl *test_firewall_policy_create
(const char *spec)
{
return g_new0(struct connman_access_firewall_policy_impl, 1);
}
static void test_firewall_policy_free
(struct connman_access_firewall_policy_impl *policy)
{
g_free(policy);
}
/*==========================================================================*
* Test driver 1
*==========================================================================*/
......@@ -112,6 +128,14 @@ static enum connman_access test1_tech_set_property
return CONNMAN_ACCESS_ALLOW;
}
static enum connman_access test1_firewall_manage
(const struct connman_access_firewall_policy_impl *policy,
const char *sender, const char *name,
enum connman_access default_access)
{
return CONNMAN_ACCESS_ALLOW;
}
static const struct connman_access_driver test1_driver = {
.name = "test1",
.default_service_policy = "allow",
......@@ -124,7 +148,10 @@ static const struct connman_access_driver test1_driver = {
.manager_policy_check = test1_manager_policy_check,
.tech_policy_create = test_tech_policy_create,
.tech_policy_free = test_tech_policy_free,
.tech_set_property = test1_tech_set_property
.tech_set_property = test1_tech_set_property,
.firewall_policy_create = test_firewall_policy_create,
.firewall_policy_free = test_firewall_policy_free,
.firewall_manage = test1_firewall_manage
};
/*==========================================================================*
......@@ -157,6 +184,14 @@ static enum connman_access test2_tech_set_property
return CONNMAN_ACCESS_DENY;
}
static enum connman_access test2_firewall_manage
(const struct connman_access_firewall_policy_impl *policy,
const char *sender, const char *name,
enum connman_access default_access)
{
return CONNMAN_ACCESS_DENY;
}
static const struct connman_access_driver test2_driver = {
.name = "test2",
.default_service_policy = "deny",
......@@ -169,7 +204,10 @@ static const struct connman_access_driver test2_driver = {
.manager_policy_check = test2_manager_policy_check,
.tech_policy_create = test_tech_policy_create,
.tech_policy_free = test_tech_policy_free,
.tech_set_property = test2_tech_set_property
.tech_set_property = test2_tech_set_property,
.firewall_policy_create = test_firewall_policy_create,
.firewall_policy_free = test_firewall_policy_free,
.firewall_manage = test2_firewall_manage
};
/*==========================================================================*
......@@ -202,11 +240,20 @@ static enum connman_access test3_tech_set_property
return default_access;
}
static enum connman_access test3_firewall_manage
(const struct connman_access_firewall_policy_impl *policy,
const char *sender, const char *name,
enum connman_access default_access)
{
return default_access;
}
static const struct connman_access_driver test3_driver = {
.name = "test3",
.service_policy_check = test3_service_policy_check,
.manager_policy_check = test3_manager_policy_check,
.tech_set_property = test3_tech_set_property
.tech_set_property = test3_tech_set_property,
.firewall_manage = test3_firewall_manage
};
/*==========================================================================*
......@@ -231,13 +278,21 @@ static struct connman_access_tech_policy_impl *test4_tech_policy_create
return NULL;
}
static struct connman_access_firewall_policy_impl *test4_firewall_policy_create
(const char *spec)
{
return NULL;
}
static const struct connman_access_driver test4_driver = {
.name = "test4",
.service_policy_create = test4_service_policy_create,
.service_policy_check = test3_service_policy_check,
.manager_policy_create = test4_manager_policy_create,
.tech_policy_create = test4_tech_policy_create,
.tech_set_property = test3_tech_set_property
.tech_set_property = test3_tech_set_property,
.firewall_policy_create = test4_firewall_policy_create,
.firewall_manage = test3_firewall_manage
};
/*==========================================================================*
......@@ -265,11 +320,19 @@ static struct connman_access_tech_policy_impl *test5_tech_policy_create
return &impl;
}
static struct connman_access_firewall_policy_impl *test5_firewall_policy_create
(const char *spec)
{
static struct connman_access_firewall_policy_impl impl;
return &impl;
}
static const struct connman_access_driver test5_driver = {
.name = "test5",
.service_policy_create = test5_service_policy_create,
.manager_policy_create = test5_manager_policy_create,
.tech_policy_create = test5_tech_policy_create
.tech_policy_create = test5_tech_policy_create,
.firewall_policy_create = test5_firewall_policy_create
};
/*==========================================================================*
......@@ -591,6 +654,66 @@ static void test_access_tech_policy()
CONNMAN_ACCESS_DENY) == CONNMAN_ACCESS_DENY);
}
static void test_access_firewall_policy()
{
struct connman_access_firewall_policy *policy;
g_assert(!__connman_access_firewall_policy_create(NULL));
g_assert(connman_access_driver_register(&test1_driver) == 0);
g_assert(connman_access_driver_register(&test2_driver) == 0);
/* test3_driver has no firewall_policy_create callback */
g_assert(connman_access_driver_register(&test3_driver) == 0);
g_assert(!__connman_access_firewall_policy_create(NULL));
connman_access_driver_unregister(&test3_driver);