bus/policy: separate prefix rules in default context
[platform/upstream/dbus.git] / bus / policy.c
index 34e8446..4bd9868 100644 (file)
@@ -22,6 +22,7 @@
  */
 
 #include <config.h>
+#include "check.h"
 #include "policy.h"
 #include "services.h"
 #include "test.h"
 #include <dbus/dbus-list.h>
 #include <dbus/dbus-hash.h>
 #include <dbus/dbus-internals.h>
+#include <dbus/dbus-message-internal.h>
+#include <dbus/dbus-connection-internal.h>
+
+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 <check user="..." /> and <check group="..." />
+         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:
-   * 
-   * <allow send_interface="foo.bar"/>
-   * <deny send_interface="*"/>
-   *
-   * (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 <include> 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, &params,
+                         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, &params,
+                         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, &params,
+                         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 */
+