X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=bus%2Fpolicy.c;h=4bd98686f2db2d61bd79048d2125f9dacc225ca0;hb=eb7c740eade2c530ca1cd795be80011028a1a5f8;hp=34e8446958f42fc7520503d6e8a9620611818d0f;hpb=6bded84c40a79c8c5fb35377920ac7dfcfef7d5d;p=platform%2Fupstream%2Fdbus.git diff --git a/bus/policy.c b/bus/policy.c index 34e8446..4bd9868 100644 --- a/bus/policy.c +++ b/bus/policy.c @@ -22,6 +22,7 @@ */ #include +#include "check.h" #include "policy.h" #include "services.h" #include "test.h" @@ -29,10 +30,24 @@ #include #include #include +#include +#include + +struct BusClientPolicy +{ + int refcount; + + BusPolicy *policy; + unsigned long *groups; + int n_groups; + dbus_uid_t uid; + dbus_bool_t uid_set; + dbus_bool_t at_console; +}; BusPolicyRule* bus_policy_rule_new (BusPolicyRuleType type, - dbus_bool_t allow) + BusPolicyRuleAccess access) { BusPolicyRule *rule; @@ -42,7 +57,7 @@ bus_policy_rule_new (BusPolicyRuleType type, rule->type = type; rule->refcount = 1; - rule->allow = allow; + rule->access = access; switch (rule->type) { @@ -54,21 +69,24 @@ bus_policy_rule_new (BusPolicyRuleType type, break; case BUS_POLICY_RULE_SEND: rule->d.send.message_type = DBUS_MESSAGE_TYPE_INVALID; - /* allow rules default to TRUE (only requested replies allowed) + * check rules default to TRUE (only requested replies are checked) * deny rules default to FALSE (only unrequested replies denied) */ - rule->d.send.requested_reply = rule->allow; + rule->d.send.requested_reply = rule->access != BUS_POLICY_RULE_ACCESS_DENY; break; case BUS_POLICY_RULE_RECEIVE: rule->d.receive.message_type = DBUS_MESSAGE_TYPE_INVALID; /* allow rules default to TRUE (only requested replies allowed) + * check rules default to TRUE (only requested replies are checked) * deny rules default to FALSE (only unrequested replies denied) */ - rule->d.receive.requested_reply = rule->allow; + rule->d.receive.requested_reply = rule->access != BUS_POLICY_RULE_ACCESS_DENY; break; case BUS_POLICY_RULE_OWN: break; + default: + _dbus_assert_not_reached ("invalid rule"); } return rule; @@ -116,8 +134,11 @@ bus_policy_rule_unref (BusPolicyRule *rule) break; case BUS_POLICY_RULE_GROUP: break; + default: + _dbus_assert_not_reached ("invalid rule"); } - + + dbus_free (rule->privilege); dbus_free (rule); } } @@ -132,8 +153,18 @@ struct BusPolicy DBusHashTable *rules_by_gid; /**< per-GID policy rules */ DBusList *at_console_true_rules; /**< console user policy rules where at_console="true"*/ DBusList *at_console_false_rules; /**< console user policy rules where at_console="false"*/ + + DBusHashTable *default_rules_by_name; + DBusList *default_prefix_rules; + unsigned int n_default_rules; }; +typedef struct BusPolicyRulesWithScore +{ + DBusList *rules; + int score; +} BusPolicyRulesWithScore; + static void free_rule_func (void *data, void *user_data) @@ -158,6 +189,21 @@ free_rule_list_func (void *data) dbus_free (list); } +static void +free_rule_list_with_score_func (void *data) +{ + BusPolicyRulesWithScore *rules = data; + + if (rules == NULL) + return; + + _dbus_list_foreach (&rules->rules, free_rule_func, NULL); + + _dbus_list_clear (&rules->rules); + + dbus_free (rules); +} + BusPolicy* bus_policy_new (void) { @@ -181,8 +227,14 @@ bus_policy_new (void) if (policy->rules_by_gid == NULL) goto failed; + policy->default_rules_by_name = _dbus_hash_table_new (DBUS_HASH_STRING, + NULL, + free_rule_list_with_score_func); + if (policy->default_rules_by_name == NULL) + goto failed; + return policy; - + failed: bus_policy_unref (policy); return NULL; @@ -230,41 +282,18 @@ bus_policy_unref (BusPolicy *policy) _dbus_hash_table_unref (policy->rules_by_gid); policy->rules_by_gid = NULL; } - - dbus_free (policy); - } -} - -static dbus_bool_t -add_list_to_client (DBusList **list, - BusClientPolicy *client) -{ - DBusList *link; - - link = _dbus_list_get_first_link (list); - while (link != NULL) - { - BusPolicyRule *rule = link->data; - link = _dbus_list_get_next_link (list, link); - switch (rule->type) + if (policy->default_rules_by_name) { - case BUS_POLICY_RULE_USER: - case BUS_POLICY_RULE_GROUP: - /* These aren't per-connection policies */ - break; - - case BUS_POLICY_RULE_OWN: - case BUS_POLICY_RULE_SEND: - case BUS_POLICY_RULE_RECEIVE: - /* These are per-connection */ - if (!bus_client_policy_append_rule (client, rule)) - return FALSE; - break; + _dbus_hash_table_unref (policy->default_rules_by_name); + policy->default_rules_by_name = NULL; } + + _dbus_list_foreach (&policy->default_prefix_rules, free_rule_func, NULL); + _dbus_list_clear (&policy->default_prefix_rules); + + dbus_free (policy); } - - return TRUE; } BusClientPolicy* @@ -273,8 +302,6 @@ bus_policy_create_client_policy (BusPolicy *policy, DBusError *error) { BusClientPolicy *client; - dbus_uid_t uid; - dbus_bool_t at_console; _dbus_assert (dbus_connection_get_is_authenticated (connection)); _DBUS_ASSERT_ERROR_IS_CLEAR (error); @@ -283,85 +310,23 @@ bus_policy_create_client_policy (BusPolicy *policy, if (client == NULL) goto nomem; - if (!add_list_to_client (&policy->default_rules, - client)) - goto nomem; - - /* we avoid the overhead of looking up user's groups - * if we don't have any group rules anyway - */ if (_dbus_hash_table_get_n_entries (policy->rules_by_gid) > 0) { - unsigned long *groups; - int n_groups; - int i; - - if (!bus_connection_get_unix_groups (connection, &groups, &n_groups, error)) + if (!bus_connection_get_unix_groups (connection, &client->groups, &client->n_groups, error)) goto failed; - - i = 0; - while (i < n_groups) - { - DBusList **list; - - list = _dbus_hash_table_lookup_uintptr (policy->rules_by_gid, - groups[i]); - - if (list != NULL) - { - if (!add_list_to_client (list, client)) - { - dbus_free (groups); - goto nomem; - } - } - - ++i; - } - - dbus_free (groups); } - if (dbus_connection_get_unix_user (connection, &uid)) + if (dbus_connection_get_unix_user (connection, &client->uid)) { - if (_dbus_hash_table_get_n_entries (policy->rules_by_uid) > 0) - { - DBusList **list; - - list = _dbus_hash_table_lookup_uintptr (policy->rules_by_uid, - uid); - - if (list != NULL) - { - if (!add_list_to_client (list, client)) - goto nomem; - } - } - - /* Add console rules */ - at_console = _dbus_unix_user_is_at_console (uid, error); + client->uid_set = TRUE; + client->at_console = _dbus_unix_user_is_at_console (client->uid, error); - if (at_console) - { - if (!add_list_to_client (&policy->at_console_true_rules, client)) - goto nomem; - } - else if (dbus_error_is_set (error) == TRUE) - { + if (dbus_error_is_set (error) == TRUE) goto failed; - } - else if (!add_list_to_client (&policy->at_console_false_rules, client)) - { - goto nomem; - } } - if (!add_list_to_client (&policy->mandatory_rules, - client)) - goto nomem; + client->policy = bus_policy_ref (policy); - bus_client_policy_optimize (client); - return client; nomem: @@ -427,7 +392,10 @@ list_allows_user (dbus_bool_t def, else continue; - allowed = rule->allow; + /* We don't intend to support and + rules. They are treated like deny. + */ + allowed = rule->access == BUS_POLICY_RULE_ACCESS_ALLOW; } return allowed; @@ -486,12 +454,94 @@ bus_policy_allow_windows_user (BusPolicy *policy, return _dbus_windows_user_is_process_owner (windows_sid); } +static BusPolicyRulesWithScore * +get_rules_by_string (DBusHashTable *hash, + const char *key) +{ + BusPolicyRulesWithScore *rules; + + rules = _dbus_hash_table_lookup_string (hash, key); + if (rules == NULL) + { + rules = dbus_new0 (BusPolicyRulesWithScore, 1); + if (rules == NULL) + return NULL; + + if (!_dbus_hash_table_insert_string (hash, (char *)key, rules)) + { + dbus_free (rules); + return NULL; + } + } + + return rules; +} + +static const char * +get_name_from_rule (BusPolicyRule *rule) +{ + const char *name = NULL; + if (rule->type == BUS_POLICY_RULE_SEND) + name = rule->d.send.destination; + else if (rule->type == BUS_POLICY_RULE_RECEIVE) + name = rule->d.receive.origin; + else if (rule->type == BUS_POLICY_RULE_OWN) + name = rule->d.own.service_name; + + if (name == NULL) + name = ""; + + return name; +} + +static dbus_bool_t +is_prefix_rule (BusPolicyRule *rule) +{ + if (rule->type == BUS_POLICY_RULE_SEND && rule->d.send.destination_prefix) + return TRUE; + if (rule->type == BUS_POLICY_RULE_OWN && rule->d.own.prefix) + return TRUE; + + return FALSE; +} + dbus_bool_t bus_policy_append_default_rule (BusPolicy *policy, BusPolicyRule *rule) { - if (!_dbus_list_append (&policy->default_rules, rule)) - return FALSE; + if (rule->type == BUS_POLICY_RULE_USER || rule->type == BUS_POLICY_RULE_GROUP) + { + if (!_dbus_list_append (&policy->default_rules, rule)) + return FALSE; + } + else + { + rule->score = ++policy->n_default_rules; + + if (is_prefix_rule (rule)) + { + if (!_dbus_list_append (&policy->default_prefix_rules, rule)) + return FALSE; + } + else + { + DBusList **list; + BusPolicyRulesWithScore *rules; + + rules = get_rules_by_string (policy->default_rules_by_name, + get_name_from_rule (rule)); + + if (rules == NULL) + return FALSE; + + list = &rules->rules; + + if (!_dbus_list_prepend (list, rule)) + return FALSE; + + rules->score = rule->score; + } + } bus_policy_rule_ref (rule); @@ -653,6 +703,64 @@ merge_id_hash (DBusHashTable *dest, return TRUE; } +static dbus_bool_t +merge_string_hash (unsigned int *n_rules, + unsigned int n_rules_to_absorb, + DBusHashTable *dest, + DBusHashTable *to_absorb) +{ + DBusHashIter iter; +#ifndef DBUS_DISABLE_ASSERT + int cnt_rules = 0; +#endif + + _dbus_hash_iter_init (to_absorb, &iter); + while (_dbus_hash_iter_next (&iter)) + { + const char *id = _dbus_hash_iter_get_string_key (&iter); + BusPolicyRulesWithScore *to_absorb_rules =_dbus_hash_iter_get_value (&iter); + DBusList **list = &to_absorb_rules->rules; + BusPolicyRulesWithScore *target_rules = get_rules_by_string (dest, id); + DBusList **target; + DBusList *list_iter; + DBusList *target_first_link; + + if (target_rules == NULL) + return FALSE; + + target = &target_rules->rules; + target_first_link = _dbus_list_get_first_link (target); + + list_iter = _dbus_list_get_first_link (list); + while (list_iter != NULL) + { + DBusList *new_link; + BusPolicyRule *rule = list_iter->data; + + rule->score += *n_rules; + list_iter = _dbus_list_get_next_link (list, list_iter); +#ifndef DBUS_DISABLE_ASSERT + cnt_rules++; +#endif + new_link = _dbus_list_alloc_link (rule); + if (new_link == NULL) + return FALSE; + + bus_policy_rule_ref (rule); + + _dbus_list_insert_before_link (target, target_first_link, new_link); + } + + target_rules->score = to_absorb_rules->score + *n_rules; + } + + _dbus_assert (n_rules_to_absorb == cnt_rules); + + *n_rules += n_rules_to_absorb; + + return TRUE; +} + dbus_bool_t bus_policy_merge (BusPolicy *policy, BusPolicy *to_absorb) @@ -685,15 +793,18 @@ bus_policy_merge (BusPolicy *policy, to_absorb->rules_by_gid)) return FALSE; - return TRUE; -} + if (!merge_string_hash (&policy->n_default_rules, + to_absorb->n_default_rules, + policy->default_rules_by_name, + to_absorb->default_rules_by_name)) + return FALSE; -struct BusClientPolicy -{ - int refcount; + if (!append_copy_of_policy_list (&policy->default_prefix_rules, + &to_absorb->default_prefix_rules)) + return FALSE; - DBusList *rules; -}; + return TRUE; +} BusClientPolicy* bus_client_policy_new (void) @@ -719,15 +830,6 @@ bus_client_policy_ref (BusClientPolicy *policy) return policy; } -static void -rule_unref_foreach (void *data, - void *user_data) -{ - BusPolicyRule *rule = data; - - bus_policy_rule_unref (rule); -} - void bus_client_policy_unref (BusClientPolicy *policy) { @@ -737,546 +839,1034 @@ bus_client_policy_unref (BusClientPolicy *policy) if (policy->refcount == 0) { - _dbus_list_foreach (&policy->rules, - rule_unref_foreach, - NULL); + if (policy->policy) + bus_policy_unref (policy->policy); - _dbus_list_clear (&policy->rules); + dbus_free (policy->groups); dbus_free (policy); } } -static void -remove_rules_by_type_up_to (BusClientPolicy *policy, - BusPolicyRuleType type, - DBusList *up_to) +#define _dbus_string_append_printf_err_check(str, fmt, args...) \ + if (!_dbus_string_append_printf(str, fmt, ##args)) \ + { \ + _dbus_string_free (str); \ + return FALSE; \ + } + +static dbus_bool_t +bus_policy_rule_to_string (BusPolicyRule *rule, + char **out_rule) { - DBusList *link; + const char *sr; + const char *access; + const char *dest; + const char *msg_type[] = {"Invalid", "method_call", "method_return", "signal", "error"}; + DBusString str; - link = _dbus_list_get_first_link (&policy->rules); - while (link != up_to) - { - BusPolicyRule *rule = link->data; - DBusList *next = _dbus_list_get_next_link (&policy->rules, link); + *out_rule = NULL; - if (rule->type == type) - { - _dbus_list_remove_link (&policy->rules, link); - bus_policy_rule_unref (rule); - } - - link = next; + switch (rule->access) + { + case BUS_POLICY_RULE_ACCESS_ALLOW: + access = "allow"; + break; + case BUS_POLICY_RULE_ACCESS_DENY: + access = "deny"; + break; + case BUS_POLICY_RULE_ACCESS_CHECK: + access = "check"; + break; + default: + _dbus_assert_not_reached ("invalid rule access value"); } -} - -void -bus_client_policy_optimize (BusClientPolicy *policy) -{ - DBusList *link; - - /* The idea here is that if we have: - * - * - * - * - * (for example) the deny will always override the allow. So we - * delete the allow. Ditto for deny followed by allow, etc. This is - * a dumb thing to put in a config file, but the feature - * of files allows for an "inheritance and override" pattern where - * it could make sense. If an included file wants to "start over" - * with a blanket deny, no point keeping the rules from the parent - * file. - */ - _dbus_verbose ("Optimizing policy with %d rules\n", - _dbus_list_get_length (&policy->rules)); - - link = _dbus_list_get_first_link (&policy->rules); - while (link != NULL) + if (rule->type == BUS_POLICY_RULE_SEND) + { + sr = "send"; + dest = "destination"; + } + else if (rule->type == BUS_POLICY_RULE_RECEIVE) { - BusPolicyRule *rule; - DBusList *next; - dbus_bool_t remove_preceding; + sr = "receive"; + dest = "sender"; + } + else + return FALSE; - next = _dbus_list_get_next_link (&policy->rules, link); - rule = link->data; - - remove_preceding = FALSE; + /* generate xml format */ + if (!_dbus_string_init (&str)) + return FALSE; - _dbus_assert (rule != NULL); - - switch (rule->type) - { - case BUS_POLICY_RULE_SEND: - remove_preceding = - rule->d.send.message_type == DBUS_MESSAGE_TYPE_INVALID && - rule->d.send.path == NULL && - rule->d.send.interface == NULL && - rule->d.send.member == NULL && - rule->d.send.error == NULL && - rule->d.send.destination == NULL; - break; - case BUS_POLICY_RULE_RECEIVE: - remove_preceding = - rule->d.receive.message_type == DBUS_MESSAGE_TYPE_INVALID && - rule->d.receive.path == NULL && - rule->d.receive.interface == NULL && - rule->d.receive.member == NULL && - rule->d.receive.error == NULL && - rule->d.receive.origin == NULL; - break; - case BUS_POLICY_RULE_OWN: - remove_preceding = - rule->d.own.service_name == NULL; - break; - case BUS_POLICY_RULE_USER: - case BUS_POLICY_RULE_GROUP: - _dbus_assert_not_reached ("invalid rule"); - break; - } + _dbus_string_append_printf_err_check (&str, "<%s ", access); - if (remove_preceding) - remove_rules_by_type_up_to (policy, rule->type, - link); - - link = next; + if (rule->d.send.destination_prefix) + { + _dbus_string_append_printf_err_check (&str, "%s_destination_prefix=\"%s\" ", sr, rule->d.send.destination); + } + else if (rule->d.send.destination) + { + _dbus_string_append_printf_err_check (&str, "%s_%s=\"%s\" ", sr, dest, rule->d.send.destination); } - _dbus_verbose ("After optimization, policy has %d rules\n", - _dbus_list_get_length (&policy->rules)); -} + if (rule->d.send.path) + _dbus_string_append_printf_err_check (&str, "%s_path=\"%s\" ", sr, rule->d.send.path); + if (rule->d.send.interface) + _dbus_string_append_printf_err_check (&str, "%s_interface=\"%s\" ", sr, rule->d.send.interface); + if (rule->d.send.member) + _dbus_string_append_printf_err_check (&str, "%s_member=\"%s\" ", sr, rule->d.send.member); + if (rule->d.send.message_type) + _dbus_string_append_printf_err_check (&str, "%s_type=\"%s\" ", sr, msg_type[rule->d.send.message_type]); + if (rule->privilege) + _dbus_string_append_printf_err_check (&str, "privilege=\"%s\" ", rule->privilege); + + if (!_dbus_string_append (&str, "/>")) + { + _dbus_string_free (&str); + return FALSE; + } -dbus_bool_t -bus_client_policy_append_rule (BusClientPolicy *policy, - BusPolicyRule *rule) -{ - _dbus_verbose ("Appending rule %p with type %d to policy %p\n", - rule, rule->type, policy); - - if (!_dbus_list_append (&policy->rules, rule)) - return FALSE; + if (!_dbus_string_steal_data (&str, out_rule)) + { + *out_rule = NULL; + _dbus_string_free (&str); + return FALSE; + } - bus_policy_rule_ref (rule); + _dbus_string_free (&str); return TRUE; } -dbus_bool_t -bus_client_policy_check_can_send (BusClientPolicy *policy, - BusRegistry *registry, - dbus_bool_t requested_reply, - DBusConnection *receiver, - DBusMessage *message, - dbus_int32_t *toggles, - dbus_bool_t *log) +typedef struct RuleParams { + enum {PARAM_SR, PARAM_OWN} type; + union { + struct { + BusRegistry *registry; + dbus_bool_t requested_reply; + DBusConnection *peer; + const char *name; + DBusMessage *message; + dbus_bool_t eavesdropping; + } sr; + const DBusString *name; + } u; +} RuleParams; + +typedef dbus_bool_t (*CheckRuleFunc) (const BusPolicyRule *, + const RuleParams *, + BusResult *, + const char **); + +static dbus_bool_t +check_send_rule (const BusPolicyRule *rule, + const RuleParams *match_params, + BusResult *result, + const char **privilege) { - DBusList *link; - dbus_bool_t allowed; - - /* policy->rules is in the order the rules appeared - * in the config file, i.e. last rule that applies wins + /* Rule is skipped if it specifies a different + * message name from the message, or a different + * destination from the message */ - - _dbus_verbose (" (policy) checking send rules\n"); - *toggles = 0; - - allowed = FALSE; - link = _dbus_list_get_first_link (&policy->rules); - while (link != NULL) + if (rule->type != BUS_POLICY_RULE_SEND) { - BusPolicyRule *rule = link->data; + _dbus_verbose (" (policy) skipping non-send rule\n"); + return FALSE; + } - link = _dbus_list_get_next_link (&policy->rules, link); - - /* Rule is skipped if it specifies a different - * message name from the message, or a different - * destination from the message - */ - - if (rule->type != BUS_POLICY_RULE_SEND) + if (rule->d.send.message_type != DBUS_MESSAGE_TYPE_INVALID) + { + if (dbus_message_get_type (match_params->u.sr.message) != rule->d.send.message_type) { - _dbus_verbose (" (policy) skipping non-send rule\n"); - continue; + _dbus_verbose (" (policy) skipping rule for different message type\n"); + return FALSE; } + } - if (rule->d.send.message_type != DBUS_MESSAGE_TYPE_INVALID) + /* If it's a reply, the requested_reply flag kicks in */ + if (dbus_message_get_reply_serial (match_params->u.sr.message) != 0) + { + /* for allow or check requested_reply=true means the rule applies + * only when reply was requested. requested_reply=false means the + * rule always applies + */ + if (!match_params->u.sr.requested_reply && rule->access != BUS_POLICY_RULE_ACCESS_DENY && rule->d.send.requested_reply && !rule->d.send.eavesdrop) { - if (dbus_message_get_type (message) != rule->d.send.message_type) - { - _dbus_verbose (" (policy) skipping rule for different message type\n"); - continue; - } + _dbus_verbose (" (policy) skipping %s rule since it only applies to requested replies and does not allow eavesdropping\n", + rule->access == BUS_POLICY_RULE_ACCESS_ALLOW ? "allow" : "check"); + return FALSE; } - /* If it's a reply, the requested_reply flag kicks in */ - if (dbus_message_get_reply_serial (message) != 0) + /* for deny, requested_reply=false means the rule applies only + * when the reply was not requested. requested_reply=true means the + * rule always applies. + */ + if (match_params->u.sr.requested_reply && rule->access == BUS_POLICY_RULE_ACCESS_DENY && !rule->d.send.requested_reply) { - /* for allow, requested_reply=true means the rule applies - * only when reply was requested. requested_reply=false means - * always allow. - */ - if (!requested_reply && rule->allow && rule->d.send.requested_reply && !rule->d.send.eavesdrop) - { - _dbus_verbose (" (policy) skipping allow rule since it only applies to requested replies and does not allow eavesdropping\n"); - continue; - } - - /* for deny, requested_reply=false means the rule applies only - * when the reply was not requested. requested_reply=true means the - * rule always applies. - */ - if (requested_reply && !rule->allow && !rule->d.send.requested_reply) - { - _dbus_verbose (" (policy) skipping deny rule since it only applies to unrequested replies\n"); - continue; - } + _dbus_verbose (" (policy) skipping deny rule since it only applies to unrequested replies\n"); + return FALSE; } - - if (rule->d.send.path != NULL) + } + + if (rule->d.send.path != NULL) + { + if (dbus_message_get_path (match_params->u.sr.message) != NULL && + strcmp (dbus_message_get_path (match_params->u.sr.message), + rule->d.send.path) != 0) { - if (dbus_message_get_path (message) != NULL && - strcmp (dbus_message_get_path (message), - rule->d.send.path) != 0) - { - _dbus_verbose (" (policy) skipping rule for different path\n"); - continue; - } + _dbus_verbose (" (policy) skipping rule for different path\n"); + return FALSE; } - - if (rule->d.send.interface != NULL) + } + + if (rule->d.send.interface != NULL) + { + /* The interface is optional in messages. For allow rules, if the message + * has no interface we want to skip the rule (and thus not allow); + * for deny rules, if the message has no interface we want to use the + * rule (and thus deny). Check rules are meant to be used like allow + * rules (they can grant access, but not remove it), so we treat it like + * allow here. + */ + dbus_bool_t no_interface; + + no_interface = dbus_message_get_interface (match_params->u.sr.message) == NULL; + + if ((no_interface && rule->access != BUS_POLICY_RULE_ACCESS_DENY) || + (!no_interface && + strcmp (dbus_message_get_interface (match_params->u.sr.message), + rule->d.send.interface) != 0)) { - /* The interface is optional in messages. For allow rules, if the message - * has no interface we want to skip the rule (and thus not allow); - * for deny rules, if the message has no interface we want to use the - * rule (and thus deny). - */ - dbus_bool_t no_interface; + _dbus_verbose (" (policy) skipping rule for different interface\n"); + return FALSE; + } + } - no_interface = dbus_message_get_interface (message) == NULL; - - if ((no_interface && rule->allow) || - (!no_interface && - strcmp (dbus_message_get_interface (message), - rule->d.send.interface) != 0)) - { - _dbus_verbose (" (policy) skipping rule for different interface\n"); - continue; - } + if (rule->d.send.member != NULL) + { + if (dbus_message_get_member (match_params->u.sr.message) != NULL && + strcmp (dbus_message_get_member (match_params->u.sr.message), + rule->d.send.member) != 0) + { + _dbus_verbose (" (policy) skipping rule for different member\n"); + return FALSE; } + } - if (rule->d.send.member != NULL) + if (rule->d.send.error != NULL) + { + if (dbus_message_get_error_name (match_params->u.sr.message) != NULL && + strcmp (dbus_message_get_error_name (match_params->u.sr.message), + rule->d.send.error) != 0) { - if (dbus_message_get_member (message) != NULL && - strcmp (dbus_message_get_member (message), - rule->d.send.member) != 0) - { - _dbus_verbose (" (policy) skipping rule for different member\n"); - continue; - } + _dbus_verbose (" (policy) skipping rule for different error name\n"); + return FALSE; } + } - if (rule->d.send.error != NULL) + if (rule->d.send.broadcast != BUS_POLICY_TRISTATE_ANY) + { + if (dbus_message_get_destination (match_params->u.sr.message) == NULL && + dbus_message_get_type (match_params->u.sr.message) == DBUS_MESSAGE_TYPE_SIGNAL) { - if (dbus_message_get_error_name (message) != NULL && - strcmp (dbus_message_get_error_name (message), - rule->d.send.error) != 0) + /* it's a broadcast */ + if (rule->d.send.broadcast == BUS_POLICY_TRISTATE_FALSE) { - _dbus_verbose (" (policy) skipping rule for different error name\n"); - continue; + _dbus_verbose (" (policy) skipping rule because message is a broadcast\n"); + return FALSE; } } - - if (rule->d.send.destination != NULL) + /* else it isn't a broadcast: there is some destination */ + else if (rule->d.send.broadcast == BUS_POLICY_TRISTATE_TRUE) + { + _dbus_verbose (" (policy) skipping rule because message is not a broadcast\n"); + return FALSE; + } + } + + if (rule->d.send.destination != NULL) + { + if (!rule->d.send.destination_prefix) { /* receiver can be NULL for messages that are sent to the * message bus itself, we check the strings in that case as * built-in services don't have a DBusConnection but messages * to them have a destination service name. + * + * Similarly, receiver can be NULL when we're deciding whether + * activation should be allowed; we make the authorization decision + * on the assumption that the activated service will have the + * requested name and no others. */ - if (receiver == NULL) + if (match_params->u.sr.peer == NULL) { - if (!dbus_message_has_destination (message, + if (!dbus_message_has_destination (match_params->u.sr.message, rule->d.send.destination)) { _dbus_verbose (" (policy) skipping rule because message dest is not %s\n", rule->d.send.destination); - continue; + return FALSE; } } else { DBusString str; BusService *service; - + _dbus_string_init_const (&str, rule->d.send.destination); - - service = bus_registry_lookup (registry, &str); + + service = bus_registry_lookup (match_params->u.sr.registry, &str); if (service == NULL) { _dbus_verbose (" (policy) skipping rule because dest %s doesn't exist\n", rule->d.send.destination); - continue; + return FALSE; } - if (!bus_service_has_owner (service, receiver)) + if (!bus_service_has_owner (service, match_params->u.sr.peer)) { _dbus_verbose (" (policy) skipping rule because dest %s isn't owned by receiver\n", rule->d.send.destination); - continue; + return FALSE; + } + } + } + else if (rule->d.send.destination_prefix) + { + /* receiver can be NULL - the same as in !send.destination_prefix */ + if (match_params->u.sr.peer == NULL) + { + const char *destination = dbus_message_get_destination (match_params->u.sr.message); + DBusString dest_name; + + if (destination == NULL) + { + _dbus_verbose (" (policy) skipping rule because message has no dest\n"); + return FALSE; + } + + _dbus_string_init_const (&dest_name, destination); + + if (!_dbus_string_starts_with_words_c_str (&dest_name, + rule->d.send.destination, + '.')) + { + _dbus_verbose (" (policy) skipping rule because message dest doesn't start with %s\n", + rule->d.send.destination); + return FALSE; + } + } + else + { + if (!bus_connection_is_service_owner_by_prefix (match_params->u.sr.peer, + rule->d.send.destination)) + { + _dbus_verbose (" (policy) skipping rule because no dest with prefix %s is owned by receiver\n", + rule->d.send.destination); + return FALSE; } } } + } - /* Use this rule */ - allowed = rule->allow; - *log = rule->d.send.log; - (*toggles)++; + if (rule->d.send.min_fds > 0 || + rule->d.send.max_fds < DBUS_MAXIMUM_MESSAGE_UNIX_FDS) + { + unsigned int n_fds = _dbus_message_get_n_unix_fds (match_params->u.sr.message); - _dbus_verbose (" (policy) used rule, allow now = %d\n", - allowed); + if (n_fds < rule->d.send.min_fds || n_fds > rule->d.send.max_fds) + { + _dbus_verbose (" (policy) skipping rule because message has %u fds " + "and that is outside range [%u,%u]", + n_fds, rule->d.send.min_fds, rule->d.send.max_fds); + return FALSE; + } } - return allowed; + /* Use this rule */ + switch (rule->access) + { + case BUS_POLICY_RULE_ACCESS_ALLOW: + *result = BUS_RESULT_TRUE; + break; + case BUS_POLICY_RULE_ACCESS_DENY: + *result = BUS_RESULT_FALSE; + break; + case BUS_POLICY_RULE_ACCESS_CHECK: + *result = BUS_RESULT_LATER; + *privilege = rule->privilege; + break; + default: + _dbus_assert_not_reached ("invalid rule access value"); + } + + return TRUE; } -/* See docs on what the args mean on bus_context_check_security_policy() - * comment - */ -dbus_bool_t -bus_client_policy_check_can_receive (BusClientPolicy *policy, - BusRegistry *registry, - dbus_bool_t requested_reply, - DBusConnection *sender, - DBusConnection *addressed_recipient, - DBusConnection *proposed_recipient, - DBusMessage *message, - dbus_int32_t *toggles) +static void +check_rules_list (const DBusList *rules, + CheckRuleFunc check_func, + const RuleParams *params, + dbus_int32_t *toggles, + dbus_bool_t *log, + BusResult *result, + const char **privilege, + BusPolicyRule **matched_rule, + dbus_bool_t break_on_first_match) { - DBusList *link; - dbus_bool_t allowed; - dbus_bool_t eavesdropping; + const DBusList *link; - eavesdropping = - addressed_recipient != proposed_recipient && - dbus_message_get_destination (message) != NULL; - - /* policy->rules is in the order the rules appeared - * in the config file, i.e. last rule that applies wins - */ - - _dbus_verbose (" (policy) checking receive rules, eavesdropping = %d\n", eavesdropping); - *toggles = 0; - - allowed = FALSE; - link = _dbus_list_get_first_link (&policy->rules); + link = _dbus_list_get_first_link ((DBusList **)&rules); while (link != NULL) { - BusPolicyRule *rule = link->data; + const BusPolicyRule *rule = link->data; - link = _dbus_list_get_next_link (&policy->rules, link); - - if (rule->type != BUS_POLICY_RULE_RECEIVE) + link = _dbus_list_get_next_link ((DBusList **)&rules, link); + + if (check_func (rule, params, result, privilege)) { - _dbus_verbose (" (policy) skipping non-receive rule\n"); - continue; + if (log) + *log = rule->d.send.log; + if (toggles) + (*toggles)++; + if (matched_rule) + *matched_rule = (BusPolicyRule *)rule; + + _dbus_verbose (" (policy) used rule, result now = %d\n", + result); + + if (break_on_first_match) + break; } + } +} + +static int +check_rules_list_with_score (DBusList *rules, + int score, + CheckRuleFunc check_func, + const RuleParams *params, + dbus_int32_t *toggles, + dbus_bool_t *log, + BusResult *result, + const char **privilege, + BusPolicyRule **matched_rule, + dbus_bool_t break_on_first_match) +{ + dbus_int32_t local_toggles; + dbus_bool_t local_log; + BusResult local_result; + const char *local_privilege; + BusPolicyRule *local_matched_rule; + + local_toggles = 0; + + check_rules_list (rules, check_func, params, + &local_toggles, &local_log, &local_result, &local_privilege, + &local_matched_rule, break_on_first_match); - if (rule->d.receive.message_type != DBUS_MESSAGE_TYPE_INVALID) + if (local_toggles > 0) + { + _dbus_assert (local_matched_rule != NULL); + + if (local_matched_rule->score > score) { - if (dbus_message_get_type (message) != rule->d.receive.message_type) + if (toggles) + *toggles += local_toggles; + if (log) + *log = local_log; + *result = local_result; + *privilege = local_privilege; + if (matched_rule) + *matched_rule = local_matched_rule; + return local_matched_rule->score; + } + } + + return score; +} + +static int +check_rules_for_name (DBusHashTable *rules, + const char *name, + int score, + CheckRuleFunc check_func, + const void *params, + dbus_int32_t *toggles, + dbus_bool_t *log, + BusResult *result, + const char **privilege, + BusPolicyRule **matched_rule) +{ + const BusPolicyRulesWithScore *rules_list; + + rules_list = _dbus_hash_table_lookup_string (rules, name); + + if (rules_list == NULL || rules_list->score <= score) + return score; + + return check_rules_list_with_score (rules_list->rules, score, check_func, params, toggles, log, result, privilege, matched_rule, TRUE); +} + +static int +find_and_check_rules_for_name (DBusHashTable *rules, + DBusList *prefix_rules, + const char *c_str, + int score, + CheckRuleFunc check_func, + const void *params, + dbus_int32_t *toggles, + dbus_bool_t *log, + BusResult *result, + const char **privilege, + BusPolicyRule **matched_rule) +{ + score = check_rules_for_name (rules, c_str, + score, check_func, params, + toggles, log, + result, privilege, + matched_rule); + + score = check_rules_list_with_score (prefix_rules, + score, check_func, params, + toggles, log, + result, privilege, + matched_rule, FALSE); + + return score; +} + +static void +find_and_check_rules (DBusHashTable *rules, + DBusList *prefix_rules, + CheckRuleFunc check_func, + const void *params, + dbus_int32_t *toggles, + dbus_bool_t *log, + BusResult *result, + const char **privilege, + BusPolicyRule **matched_rule) +{ + const RuleParams *p = params; + const DBusList *services = NULL; + int score = 0; + + if (p->type == PARAM_SR) + { + if (p->u.sr.peer != NULL) + { + DBusList *link; + + services = bus_connection_get_owned_services_list (p->u.sr.peer); + + link = _dbus_list_get_first_link ((DBusList **)&services); + while (link != NULL) { - _dbus_verbose (" (policy) skipping rule for different message type\n"); - continue; + const char *name = bus_service_get_name (link->data); + + link = _dbus_list_get_next_link ((DBusList **)&services, link); + + /* skip unique id names */ + if (name[0] == ':') + continue; + + score = find_and_check_rules_for_name (rules, prefix_rules, name, score, + check_func, params, + toggles, log, result, + privilege, matched_rule); } } + else if (p->u.sr.name != NULL) + { + score = find_and_check_rules_for_name (rules, prefix_rules, p->u.sr.name, score, + check_func, params, + toggles, log, result, + privilege, matched_rule); + } + } + else + score = find_and_check_rules_for_name (rules, prefix_rules, _dbus_string_get_const_data(p->u.name), + score, check_func, params, + toggles, log, result, + privilege, matched_rule); + + /* check also wildcard rules */ + score = check_rules_for_name (rules, "", score, check_func, params, + toggles, log, result, privilege, matched_rule); +} + +static BusResult +check_policy (BusClientPolicy *policy, + CheckRuleFunc check_func, + const void *params, + dbus_int32_t *toggles, + dbus_bool_t *log, + const char **privilege, + BusPolicyRule **matched_rule) +{ + BusResult result = BUS_RESULT_FALSE; + + if (toggles) + *toggles = 0; + + find_and_check_rules (policy->policy->default_rules_by_name, + policy->policy->default_prefix_rules, + check_func, params, + toggles, log, &result, privilege, matched_rule); + + /* we avoid the overhead of looking up user's groups + * if we don't have any group rules anyway + */ + if (_dbus_hash_table_get_n_entries (policy->policy->rules_by_gid) > 0) + { + int i; + + for (i = 0; i < policy->n_groups; ++i) + { + const DBusList **list; + + list = _dbus_hash_table_lookup_uintptr (policy->policy->rules_by_gid, + policy->groups[i]); + + if (list != NULL) + check_rules_list (*list, check_func, params, + toggles, log, &result, privilege, matched_rule, FALSE); + } + } + + if (policy->uid_set) + { + if (_dbus_hash_table_get_n_entries (policy->policy->rules_by_uid) > 0) + { + const DBusList **list; + + list = _dbus_hash_table_lookup_uintptr (policy->policy->rules_by_uid, + policy->uid); + + if (list != NULL) + check_rules_list (*list, check_func, params, + toggles, log, &result, privilege, matched_rule, FALSE); + + if (policy->at_console) + check_rules_list (policy->policy->at_console_true_rules, check_func, + params, toggles, log, &result, privilege, matched_rule, FALSE); + else + check_rules_list (policy->policy->at_console_false_rules, check_func, + params, toggles, log, &result, privilege, matched_rule, FALSE); + } + } + + check_rules_list (policy->policy->mandatory_rules, check_func, params, + toggles, log, &result, privilege, matched_rule, FALSE); + + return result; +} + +BusResult +bus_client_policy_check_can_send (DBusConnection *sender, + BusClientPolicy *policy, + BusRegistry *registry, + dbus_bool_t requested_reply, + DBusConnection *addressed_recipient, + DBusConnection *receiver, + DBusMessage *message, + dbus_int32_t *toggles, + dbus_bool_t *log, + const char **privilege_param, + BusDeferredMessage **deferred_message, + char **out_rule) +{ + BusResult result; + const char *privilege; + BusPolicyRule *matched_rule = NULL; + struct RuleParams params; + + params.type = PARAM_SR; + params.u.sr.registry = registry; + params.u.sr.requested_reply = requested_reply; + params.u.sr.peer = receiver; + params.u.sr.message = message; + params.u.sr.name = dbus_message_get_destination (message); + + _dbus_verbose (" (policy) checking send rules\n"); + + result = check_policy (policy, check_send_rule, ¶ms, + toggles, log, &privilege, &matched_rule); + + if (result == BUS_RESULT_LATER) + { + BusContext *context = bus_connection_get_context(sender); + BusCheck *check = bus_context_get_check(context); + + result = bus_check_privilege(check, message, sender, addressed_recipient, receiver, + privilege, BUS_DEFERRED_MESSAGE_CHECK_SEND, deferred_message); + if (result == BUS_RESULT_LATER && deferred_message != NULL) + bus_deferred_message_set_policy_check_info(*deferred_message, requested_reply, + *toggles, privilege); + } + else + privilege = NULL; + + if (privilege_param != NULL) + *privilege_param = privilege; + + if (result == BUS_RESULT_FALSE) + { + if (matched_rule && out_rule) + bus_policy_rule_to_string (matched_rule, out_rule); + } + + return result; +} + +static dbus_bool_t +check_receive_rule (const BusPolicyRule *rule, + const RuleParams *match_params, + BusResult *result, + const char **privilege) +{ + if (rule->type != BUS_POLICY_RULE_RECEIVE) + { + _dbus_verbose (" (policy) skipping non-receive rule\n"); + return FALSE; + } + + if (rule->d.receive.message_type != DBUS_MESSAGE_TYPE_INVALID) + { + if (dbus_message_get_type (match_params->u.sr.message) != rule->d.receive.message_type) + { + _dbus_verbose (" (policy) skipping rule for different message type\n"); + return FALSE; + } + } + + + /* for allow or check, eavesdrop=false means the rule doesn't apply when + * eavesdropping. eavesdrop=true means the rule always applies + */ + if (match_params->u.sr.eavesdropping && rule->access != BUS_POLICY_RULE_ACCESS_DENY && !rule->d.receive.eavesdrop) + { + _dbus_verbose (" (policy) skipping %s rule since it doesn't apply to eavesdropping\n", + rule->access == BUS_POLICY_RULE_ACCESS_ALLOW ? "allow" : "check"); + return FALSE; + } + + /* for deny, eavesdrop=true means the rule applies only when + * eavesdropping; eavesdrop=false means always deny. + */ + if (!match_params->u.sr.eavesdropping && rule->access == BUS_POLICY_RULE_ACCESS_DENY && rule->d.receive.eavesdrop) + { + _dbus_verbose (" (policy) skipping deny rule since it only applies to eavesdropping\n"); + return FALSE; + } + + /* If it's a reply, the requested_reply flag kicks in */ + if (dbus_message_get_reply_serial (match_params->u.sr.message) != 0) + { + /* for allow or check requested_reply=true means the rule applies + * only when reply was requested. requested_reply=false means the + * rule always applies + */ + if (!match_params->u.sr.requested_reply && rule->access != BUS_POLICY_RULE_ACCESS_DENY && rule->d.receive.requested_reply && !rule->d.receive.eavesdrop) + { + _dbus_verbose (" (policy) skipping %s rule since it only applies to requested replies and does not allow eavesdropping\n", + rule->access == BUS_POLICY_RULE_ACCESS_DENY ? "allow" : "deny"); + return FALSE; + } - /* for allow, eavesdrop=false means the rule doesn't apply when - * eavesdropping. eavesdrop=true means always allow. + /* for deny, requested_reply=false means the rule applies only + * when the reply was not requested. requested_reply=true means the + * rule always applies. */ - if (eavesdropping && rule->allow && !rule->d.receive.eavesdrop) + if (match_params->u.sr.requested_reply && rule->access == BUS_POLICY_RULE_ACCESS_DENY && !rule->d.receive.requested_reply) { - _dbus_verbose (" (policy) skipping allow rule since it doesn't apply to eavesdropping\n"); - continue; + _dbus_verbose (" (policy) skipping deny rule since it only applies to unrequested replies\n"); + return FALSE; } + } + + if (rule->d.receive.path != NULL) + { + if (dbus_message_get_path (match_params->u.sr.message) != NULL && + strcmp (dbus_message_get_path (match_params->u.sr.message), + rule->d.receive.path) != 0) + { + _dbus_verbose (" (policy) skipping rule for different path\n"); + return FALSE; + } + } - /* for deny, eavesdrop=true means the rule applies only when - * eavesdropping; eavesdrop=false means always deny. + if (rule->d.receive.interface != NULL) + { + /* The interface is optional in messages. For allow rules, if the message + * has no interface we want to skip the rule (and thus not allow); + * for deny rules, if the message has no interface we want to use the + * rule (and thus deny). Check rules are treated like allow rules. */ - if (!eavesdropping && !rule->allow && rule->d.receive.eavesdrop) + dbus_bool_t no_interface; + + no_interface = dbus_message_get_interface (match_params->u.sr.message) == NULL; + + if ((no_interface && rule->access != BUS_POLICY_RULE_ACCESS_DENY) || + (!no_interface && + strcmp (dbus_message_get_interface (match_params->u.sr.message), + rule->d.receive.interface) != 0)) { - _dbus_verbose (" (policy) skipping deny rule since it only applies to eavesdropping\n"); - continue; + _dbus_verbose (" (policy) skipping rule for different interface\n"); + return FALSE; } + } - /* If it's a reply, the requested_reply flag kicks in */ - if (dbus_message_get_reply_serial (message) != 0) + if (rule->d.receive.member != NULL) + { + if (dbus_message_get_member (match_params->u.sr.message) != NULL && + strcmp (dbus_message_get_member (match_params->u.sr.message), + rule->d.receive.member) != 0) { - /* for allow, requested_reply=true means the rule applies - * only when reply was requested. requested_reply=false means - * always allow. - */ - if (!requested_reply && rule->allow && rule->d.receive.requested_reply && !rule->d.receive.eavesdrop) - { - _dbus_verbose (" (policy) skipping allow rule since it only applies to requested replies and does not allow eavesdropping\n"); - continue; - } + _dbus_verbose (" (policy) skipping rule for different member\n"); + return FALSE; + } + } - /* for deny, requested_reply=false means the rule applies only - * when the reply was not requested. requested_reply=true means the - * rule always applies. - */ - if (requested_reply && !rule->allow && !rule->d.receive.requested_reply) - { - _dbus_verbose (" (policy) skipping deny rule since it only applies to unrequested replies\n"); - continue; - } + if (rule->d.receive.error != NULL) + { + if (dbus_message_get_error_name (match_params->u.sr.message) != NULL && + strcmp (dbus_message_get_error_name (match_params->u.sr.message), + rule->d.receive.error) != 0) + { + _dbus_verbose (" (policy) skipping rule for different error name\n"); + return FALSE; } - - if (rule->d.receive.path != NULL) + } + + if (rule->d.receive.origin != NULL) + { + /* sender can be NULL for messages that originate from the + * message bus itself, we check the strings in that case as + * built-in services don't have a DBusConnection but will + * still set the sender on their messages. + */ + if (match_params->u.sr.peer == NULL) { - if (dbus_message_get_path (message) != NULL && - strcmp (dbus_message_get_path (message), - rule->d.receive.path) != 0) + if (!dbus_message_has_sender (match_params->u.sr.message, + rule->d.receive.origin)) { - _dbus_verbose (" (policy) skipping rule for different path\n"); - continue; + _dbus_verbose (" (policy) skipping rule because message sender is not %s\n", + rule->d.receive.origin); + return FALSE; } } - - if (rule->d.receive.interface != NULL) + else { - /* The interface is optional in messages. For allow rules, if the message - * has no interface we want to skip the rule (and thus not allow); - * for deny rules, if the message has no interface we want to use the - * rule (and thus deny). - */ - dbus_bool_t no_interface; + BusService *service; + DBusString str; - no_interface = dbus_message_get_interface (message) == NULL; + _dbus_string_init_const (&str, rule->d.receive.origin); + + service = bus_registry_lookup (match_params->u.sr.registry, &str); - if ((no_interface && rule->allow) || - (!no_interface && - strcmp (dbus_message_get_interface (message), - rule->d.receive.interface) != 0)) + if (service == NULL) { - _dbus_verbose (" (policy) skipping rule for different interface\n"); - continue; + _dbus_verbose (" (policy) skipping rule because origin %s doesn't exist\n", + rule->d.receive.origin); + return FALSE; } - } - if (rule->d.receive.member != NULL) - { - if (dbus_message_get_member (message) != NULL && - strcmp (dbus_message_get_member (message), - rule->d.receive.member) != 0) + if (!bus_service_has_owner (service, match_params->u.sr.peer)) { - _dbus_verbose (" (policy) skipping rule for different member\n"); - continue; + _dbus_verbose (" (policy) skipping rule because origin %s isn't owned by sender\n", + rule->d.receive.origin); + return FALSE; } } + } + + if (rule->d.receive.min_fds > 0 || + rule->d.receive.max_fds < DBUS_MAXIMUM_MESSAGE_UNIX_FDS) + { + unsigned int n_fds = _dbus_message_get_n_unix_fds (match_params->u.sr.message); - if (rule->d.receive.error != NULL) + if (n_fds < rule->d.receive.min_fds || n_fds > rule->d.receive.max_fds) { - if (dbus_message_get_error_name (message) != NULL && - strcmp (dbus_message_get_error_name (message), - rule->d.receive.error) != 0) - { - _dbus_verbose (" (policy) skipping rule for different error name\n"); - continue; - } + _dbus_verbose (" (policy) skipping rule because message has %u fds " + "and that is outside range [%u,%u]", + n_fds, rule->d.receive.min_fds, + rule->d.receive.max_fds); + return FALSE; } - - if (rule->d.receive.origin != NULL) - { - /* sender can be NULL for messages that originate from the - * message bus itself, we check the strings in that case as - * built-in services don't have a DBusConnection but will - * still set the sender on their messages. - */ - if (sender == NULL) - { - if (!dbus_message_has_sender (message, - rule->d.receive.origin)) - { - _dbus_verbose (" (policy) skipping rule because message sender is not %s\n", - rule->d.receive.origin); - continue; - } - } - else - { - BusService *service; - DBusString str; + } - _dbus_string_init_const (&str, rule->d.receive.origin); - - service = bus_registry_lookup (registry, &str); - - if (service == NULL) - { - _dbus_verbose (" (policy) skipping rule because origin %s doesn't exist\n", - rule->d.receive.origin); - continue; - } + /* Use this rule */ + switch (rule->access) + { + case BUS_POLICY_RULE_ACCESS_ALLOW: + *result = BUS_RESULT_TRUE; + break; + case BUS_POLICY_RULE_ACCESS_DENY: + *result = BUS_RESULT_FALSE; + break; + case BUS_POLICY_RULE_ACCESS_CHECK: + *result = BUS_RESULT_LATER; + *privilege = rule->privilege; + break; + default: + _dbus_assert_not_reached ("invalid rule access value"); + } - if (!bus_service_has_owner (service, sender)) - { - _dbus_verbose (" (policy) skipping rule because origin %s isn't owned by sender\n", - rule->d.receive.origin); - continue; - } - } - } - - /* Use this rule */ - allowed = rule->allow; - (*toggles)++; + return TRUE; +} - _dbus_verbose (" (policy) used rule, allow now = %d\n", - allowed); +/* See docs on what the args mean on bus_context_check_security_policy() + * comment + */ +BusResult +bus_client_policy_check_can_receive (BusClientPolicy *policy, + BusRegistry *registry, + dbus_bool_t requested_reply, + DBusConnection *sender, + DBusConnection *addressed_recipient, + DBusConnection *proposed_recipient, + DBusMessage *message, + dbus_int32_t *toggles, + const char **privilege_param, + BusDeferredMessage **deferred_message, + char **out_rule) +{ + BusResult result; + const char *privilege; + BusPolicyRule *matched_rule = NULL; + struct RuleParams params; + + params.type = PARAM_SR; + params.u.sr.registry = registry; + params.u.sr.requested_reply = requested_reply; + params.u.sr.peer = sender; + params.u.sr.message = message; + params.u.sr.eavesdropping = + addressed_recipient != proposed_recipient && + dbus_message_get_destination (message) != NULL; + params.u.sr.name = dbus_message_get_sender (message); + + _dbus_verbose (" (policy) checking receive rules, eavesdropping = %d\n", params.u.sr.eavesdropping); + + result = check_policy (policy, check_receive_rule, ¶ms, + toggles, NULL, &privilege, &matched_rule); + + if (result == BUS_RESULT_LATER) + { + BusContext *context = bus_connection_get_context(proposed_recipient); + BusCheck *check = bus_context_get_check(context); + + result = bus_check_privilege(check, message, sender, addressed_recipient, proposed_recipient, + privilege, BUS_DEFERRED_MESSAGE_CHECK_RECEIVE, deferred_message); + if (result == BUS_RESULT_LATER && deferred_message != NULL) + bus_deferred_message_set_policy_check_info(*deferred_message, requested_reply, + *toggles, privilege); } + else + privilege = NULL; - return allowed; + if (privilege_param != NULL) + *privilege_param = privilege; + + if (result == BUS_RESULT_FALSE) + { + if (matched_rule && out_rule) + bus_policy_rule_to_string (matched_rule, out_rule); + } + + return result; } -dbus_bool_t +static dbus_bool_t +check_own_rule (const BusPolicyRule *rule, + const RuleParams *params, + BusResult *result, + const char **privilege) +{ + const DBusString *service_name = params->u.name; + + /* Rule is skipped if it specifies a different service name from + * the desired one. + */ + + if (rule->type != BUS_POLICY_RULE_OWN) + return FALSE; + + if (!rule->d.own.prefix && rule->d.own.service_name != NULL) + { + if (!_dbus_string_equal_c_str (service_name, + rule->d.own.service_name)) + return FALSE; + } + else if (rule->d.own.prefix) + { + if (!_dbus_string_starts_with_words_c_str (service_name, + rule->d.own.service_name, + '.')) + return FALSE; + } + + /* Use this rule */ + switch (rule->access) + { + case BUS_POLICY_RULE_ACCESS_ALLOW: + *result = BUS_RESULT_TRUE; + break; + case BUS_POLICY_RULE_ACCESS_DENY: + *result = BUS_RESULT_FALSE; + break; + case BUS_POLICY_RULE_ACCESS_CHECK: + *result = BUS_RESULT_LATER; + *privilege = rule->privilege; + break; + default: + _dbus_assert_not_reached ("invalid rule access value"); + } + + return TRUE; +} + +BusResult bus_client_policy_check_can_own (BusClientPolicy *policy, + const DBusString *service_name, DBusConnection *connection, - const DBusString *service_name) + DBusMessage *message) { - DBusList *link; - dbus_bool_t allowed; + BusResult result; + const char *privilege; + RuleParams params; + + params.type = PARAM_OWN; + params.u.name = service_name; - /* policy->rules is in the order the rules appeared - * in the config file, i.e. last rule that applies wins - */ + result = check_policy (policy, check_own_rule, ¶ms, + NULL, NULL, &privilege, NULL); - allowed = FALSE; - link = _dbus_list_get_first_link (&policy->rules); - while (link != NULL) + if (result == BUS_RESULT_LATER) { - BusPolicyRule *rule = link->data; - - link = _dbus_list_get_next_link (&policy->rules, link); - - /* Rule is skipped if it specifies a different service name from - * the desired one. - */ - - if (rule->type != BUS_POLICY_RULE_OWN) - continue; + BusContext *context = bus_connection_get_context(connection); + BusCheck *check = bus_context_get_check(context); + BusDeferredMessage *deferred_message = NULL; - if (rule->d.own.service_name != NULL) + result = bus_check_privilege(check, message, connection, NULL, NULL, + privilege, BUS_DEFERRED_MESSAGE_CHECK_OWN, &deferred_message); + if (result == BUS_RESULT_LATER) { - if (!_dbus_string_equal_c_str (service_name, - rule->d.own.service_name)) - continue; + bus_deferred_message_disable_sender(deferred_message); } - - /* Use this rule */ - allowed = rule->allow; } - return allowed; + return result; } + +#ifdef DBUS_ENABLE_EMBEDDED_TESTS +dbus_bool_t +bus_policy_check_can_own (BusPolicy *policy, + const DBusString *service_name) +{ + return bus_rules_check_can_own (policy->default_rules, service_name, NULL, NULL); +} +#endif /* DBUS_ENABLE_EMBEDDED_TESTS */ +