/* * This file is part of Cockpit. * * Copyright (C) 2014 Red Hat, Inc. * * Cockpit is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * Cockpit is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Cockpit; If not, see . */ #include "config.h" #include "cockpitdbusrules.h" #include "cockpitpaths.h" #include #include /* * These are rules similar to what a dbus-daemon would use when processing * AddMatch based forwarding. These are used to identify which signals a * client wanted to subscribe to, and which paths/interfaces a client * wanted to watch. * * We use several speed ups to quickly identify paths that are not matched * by the rules ... before then going into matching each rule in turn. * * An empty rule set forwards nothing. It has a fast bypass flag which * disables all the logic. */ typedef struct { gint refs; gchar *path; gboolean is_namespace; gchar *interface; gchar *member; gchar *arg0; } RuleData; static guint32 rule_hash (gconstpointer data) { const RuleData *rule = data; return g_str_hash (rule->path) ^ g_int_hash (&rule->is_namespace) ^ (rule->interface ? g_str_hash (rule->interface) : 0) ^ (rule->member ? g_str_hash (rule->member) : 0) ^ (rule->arg0 ? g_str_hash (rule->arg0) : 0); } static gboolean rule_equal (gconstpointer one, gconstpointer two) { const RuleData *r1 = one; const RuleData *r2 = two; return r1->is_namespace == r2->is_namespace && g_str_equal (r1->path, r2->path) && g_strcmp0 (r1->interface, r2->interface) == 0; } static void rule_dump (RuleData *rule, GString *string) { g_string_append (string, "{ "); if (rule->path) g_string_append_printf (string, "%s: \"%s\", ", rule->is_namespace ? "path_namespace" : "path", rule->path); if (rule->interface) g_string_append_printf (string, "interface: \"%s\", ", rule->interface); if (rule->arg0) g_string_append_printf (string, "arg0: \"%s\", ", rule->arg0); if (rule->member) g_string_append_printf (string, "member: \"%s\", ", rule->member); g_string_append (string, "}"); } static void rule_free (gpointer data) { RuleData *rule = data; g_free (rule->path); g_free (rule->interface); g_free (rule->arg0); g_free (rule->member); g_slice_free (RuleData, rule); } struct _CockpitDBusRules { GHashTable *all; GHashTable *paths; GTree *path_namespaces; gboolean all_paths; gboolean only_paths; gboolean nothing; }; gchar * cockpit_dbus_rules_to_string (CockpitDBusRules *rules) { GHashTableIter iter; GString *string; RuleData *rule; string = g_string_new ("[ "); g_hash_table_iter_init (&iter, rules->all); while (g_hash_table_iter_next (&iter, (gpointer *)&rule, NULL)) { rule_dump (rule, string); g_string_append (string, ", "); } g_string_append (string, "]"); return g_string_free (string, FALSE); } static gboolean rule_match (RuleData *rule, const gchar *path, const gchar *interface, const gchar *member, const gchar *arg0) { if (!g_str_equal (rule->path, path)) { if (!rule->is_namespace || !cockpit_path_equal_or_ancestor (path, rule->path)) return FALSE; } if (interface && rule->interface && strcmp (interface, rule->interface) != 0) return FALSE; if (member && rule->member && strcmp (member, rule->member) != 0) return FALSE; /* Note that if arg0 came in as NULL, it means message doesn't have arg0: no match */ if (rule->arg0 && g_strcmp0 (arg0, rule->arg0) != 0) return FALSE; return TRUE; } gboolean cockpit_dbus_rules_match (CockpitDBusRules *rules, const gchar *path, const gchar *interface, const gchar *member, const gchar *arg0) { GHashTableIter iter; RuleData *rule; g_return_val_if_fail (path != NULL, FALSE); if (rules->nothing) return FALSE; if (!rules->all_paths) { if (!cockpit_paths_contain_or_ancestor (rules->path_namespaces, path) && !g_hash_table_lookup (rules->paths, path)) return FALSE; } if (rules->only_paths) return TRUE; g_hash_table_iter_init (&iter, rules->all); while (g_hash_table_iter_next (&iter, (gpointer *)&rule, NULL)) { if (rule_match (rule, path, interface, member, arg0)) return TRUE; } return FALSE; } CockpitDBusRules * cockpit_dbus_rules_new (void) { CockpitDBusRules *rules = g_new0 (CockpitDBusRules, 1); rules->nothing = TRUE; return rules; } static RuleData * rule_lookup (CockpitDBusRules *rules, const gchar *path, gboolean is_namespace, const gchar *interface, const gchar *member, const gchar *arg0) { RuleData key = { .refs = 0, .path = (gchar *)path, .is_namespace = is_namespace, .interface = (gchar *)interface, .member = (gchar *)member, .arg0 = (gchar *)arg0 }; return g_hash_table_lookup (rules->all, &key); } static void recompile_rules (CockpitDBusRules *rules) { GHashTableIter iter; RuleData *rule; if (rules->paths) g_hash_table_remove_all (rules->paths); else rules->paths = g_hash_table_new (g_str_hash, g_str_equal); if (rules->path_namespaces) g_tree_destroy (rules->path_namespaces); rules->path_namespaces = cockpit_paths_new (); rules->all_paths = FALSE; rules->nothing = TRUE; rules->only_paths = TRUE; g_hash_table_iter_init (&iter, rules->all); while (g_hash_table_iter_next (&iter, (gpointer *)&rule, NULL)) { rules->nothing = FALSE; if (rule->is_namespace) { if (g_str_equal (rule->path, "/")) rules->all_paths = TRUE; cockpit_paths_add (rules->path_namespaces, rule->path); } else { g_hash_table_add (rules->paths, rule->path); } if (rule->interface || rule->member || rule->arg0) rules->only_paths = FALSE; } } gboolean cockpit_dbus_rules_add (CockpitDBusRules *rules, const gchar *path, gboolean is_namespace, const gchar *interface, const gchar *member, const gchar *arg0) { RuleData *rule = NULL; if (!path) { path = "/"; is_namespace = TRUE; } if (!rules->nothing) rule = rule_lookup (rules, path, is_namespace, interface, member, arg0); if (rules->all == NULL) rules->all = g_hash_table_new_full (rule_hash, rule_equal, rule_free, NULL); if (rule == NULL) { rule = g_slice_new0 (RuleData); rule->refs = 1; rule->path = g_strdup (path); rule->is_namespace = is_namespace; rule->interface = g_strdup (interface); rule->member = g_strdup (member); rule->arg0 = g_strdup (arg0); g_hash_table_add (rules->all, rule); recompile_rules (rules); return TRUE; } else { rule->refs++; return FALSE; } } gboolean cockpit_dbus_rules_remove (CockpitDBusRules *rules, const gchar *path, gboolean is_namespace, const gchar *interface, const gchar *member, const gchar *arg0) { RuleData *rule = NULL; if (!path) { path = "/"; is_namespace = TRUE; } if (rules->nothing) return FALSE; rule = rule_lookup (rules, path, is_namespace, interface, member, arg0); if (rule == NULL) return FALSE; rule->refs--; if (rule->refs == 0) { g_hash_table_remove (rules->all, rule); recompile_rules (rules); return TRUE; } return FALSE; } void cockpit_dbus_rules_free (CockpitDBusRules *rules) { if (rules->all) g_hash_table_destroy (rules->all); if (rules->paths) g_hash_table_destroy (rules->paths); if (rules->path_namespaces) g_tree_destroy (rules->path_namespaces); g_free (rules); }