[daemon-dev][daemon-fix] starting services by direct message (autostart) and some...
[platform/upstream/dbus.git] / bus / policy.c
index 0a002a8..082f385 100644 (file)
@@ -1,9 +1,9 @@
-/* -*- mode: C; c-file-style: "gnu" -*- */
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
 /* policy.c  Bus security policy
  *
- * Copyright (C) 2003  Red Hat, Inc.
+ * Copyright (C) 2003, 2004  Red Hat, Inc.
  *
- * Licensed under the Academic Free License version 1.2
+ * Licensed under the Academic Free License version 2.1
  * 
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * 
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  *
  */
 
+#include <config.h>
 #include "policy.h"
 #include "services.h"
 #include "test.h"
+#include "utils.h"
 #include <dbus/dbus-list.h>
 #include <dbus/dbus-hash.h>
 #include <dbus/dbus-internals.h>
@@ -51,7 +53,20 @@ bus_policy_rule_new (BusPolicyRuleType type,
       rule->d.group.gid = DBUS_GID_UNSET;
       break;
     case BUS_POLICY_RULE_SEND:
+      rule->d.send.message_type = DBUS_MESSAGE_TYPE_INVALID;
+
+      /* allow rules default to TRUE (only requested replies allowed)
+       * deny rules default to FALSE (only unrequested replies denied)
+       */
+      rule->d.send.requested_reply = rule->allow;
+      break;
     case BUS_POLICY_RULE_RECEIVE:
+      rule->d.receive.message_type = DBUS_MESSAGE_TYPE_INVALID;
+      /* allow rules default to TRUE (only requested replies allowed)
+       * deny rules default to FALSE (only unrequested replies denied)
+       */
+      rule->d.receive.requested_reply = rule->allow;
+      break;
     case BUS_POLICY_RULE_OWN:
       break;
     }
@@ -59,12 +74,14 @@ bus_policy_rule_new (BusPolicyRuleType type,
   return rule;
 }
 
-void
+BusPolicyRule *
 bus_policy_rule_ref (BusPolicyRule *rule)
 {
   _dbus_assert (rule->refcount > 0);
 
   rule->refcount += 1;
+
+  return rule;
 }
 
 void
@@ -79,11 +96,17 @@ bus_policy_rule_unref (BusPolicyRule *rule)
       switch (rule->type)
         {
         case BUS_POLICY_RULE_SEND:
-          dbus_free (rule->d.send.message_name);
+          dbus_free (rule->d.send.path);
+          dbus_free (rule->d.send.interface);
+          dbus_free (rule->d.send.member);
+          dbus_free (rule->d.send.error);
           dbus_free (rule->d.send.destination);
           break;
         case BUS_POLICY_RULE_RECEIVE:
-          dbus_free (rule->d.receive.message_name);
+          dbus_free (rule->d.receive.path);
+          dbus_free (rule->d.receive.interface);
+          dbus_free (rule->d.receive.member);
+          dbus_free (rule->d.receive.error);
           dbus_free (rule->d.receive.origin);
           break;
         case BUS_POLICY_RULE_OWN:
@@ -103,10 +126,12 @@ struct BusPolicy
 {
   int refcount;
 
-  DBusList *default_rules;       /**< Default policy rules */
-  DBusList *mandatory_rules;     /**< Mandatory policy rules */
-  DBusHashTable *rules_by_uid;   /**< per-UID policy rules */
-  DBusHashTable *rules_by_gid;   /**< per-GID policy rules */
+  DBusList *default_rules;         /**< Default policy rules */
+  DBusList *mandatory_rules;       /**< Mandatory policy rules */
+  DBusHashTable *rules_by_uid;     /**< per-UID policy rules */
+  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"*/
 };
 
 static void
@@ -123,6 +148,9 @@ free_rule_list_func (void *data)
 {
   DBusList **list = data;
 
+  if (list == NULL) /* DBusHashTable is on crack */
+    return;
+  
   _dbus_list_foreach (list, free_rule_func, NULL);
   
   _dbus_list_clear (list);
@@ -141,18 +169,18 @@ bus_policy_new (void)
 
   policy->refcount = 1;
   
-  policy->rules_by_uid = _dbus_hash_table_new (DBUS_HASH_ULONG,
+  policy->rules_by_uid = _dbus_hash_table_new (DBUS_HASH_UINTPTR,
                                                NULL,
                                                free_rule_list_func);
   if (policy->rules_by_uid == NULL)
     goto failed;
 
-  policy->rules_by_gid = _dbus_hash_table_new (DBUS_HASH_ULONG,
+  policy->rules_by_gid = _dbus_hash_table_new (DBUS_HASH_UINTPTR,
                                                NULL,
                                                free_rule_list_func);
   if (policy->rules_by_gid == NULL)
     goto failed;
-  
+
   return policy;
   
  failed:
@@ -160,12 +188,14 @@ bus_policy_new (void)
   return NULL;
 }
 
-void
+BusPolicy *
 bus_policy_ref (BusPolicy *policy)
 {
   _dbus_assert (policy->refcount > 0);
 
   policy->refcount += 1;
+
+  return policy;
 }
 
 void
@@ -182,7 +212,13 @@ bus_policy_unref (BusPolicy *policy)
 
       _dbus_list_foreach (&policy->mandatory_rules, free_rule_func, NULL);
       _dbus_list_clear (&policy->mandatory_rules);
-      
+
+      _dbus_list_foreach (&policy->at_console_true_rules, free_rule_func, NULL);
+      _dbus_list_clear (&policy->at_console_true_rules);
+
+      _dbus_list_foreach (&policy->at_console_false_rules, free_rule_func, NULL);
+      _dbus_list_clear (&policy->at_console_false_rules);
+
       if (policy->rules_by_uid)
         {
           _dbus_hash_table_unref (policy->rules_by_uid);
@@ -233,20 +269,23 @@ add_list_to_client (DBusList        **list,
 
 BusClientPolicy*
 bus_policy_create_client_policy (BusPolicy      *policy,
-                                 DBusConnection *connection)
+                                 DBusConnection *connection,
+                                 DBusError      *error)
 {
   BusClientPolicy *client;
-  unsigned long uid;
+  dbus_uid_t uid;
+  dbus_bool_t at_console;
 
   _dbus_assert (dbus_connection_get_is_authenticated (connection));
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
   
   client = bus_client_policy_new ();
   if (client == NULL)
-    return NULL;
+    goto nomem;
 
   if (!add_list_to_client (&policy->default_rules,
                            client))
-    goto failed;
+    goto nomem;
 
   /* we avoid the overhead of looking up user's groups
    * if we don't have any group rules anyway
@@ -257,7 +296,7 @@ bus_policy_create_client_policy (BusPolicy      *policy,
       int n_groups;
       int i;
       
-      if (!bus_connection_get_groups (connection, &groups, &n_groups))
+      if (!bus_connection_get_unix_groups (connection, &groups, &n_groups, error))
         goto failed;
       
       i = 0;
@@ -265,7 +304,7 @@ bus_policy_create_client_policy (BusPolicy      *policy,
         {
           DBusList **list;
           
-          list = _dbus_hash_table_lookup_ulong (policy->rules_by_gid,
+          list = _dbus_hash_table_lookup_uintptr (policy->rules_by_gid,
                                                 groups[i]);
           
           if (list != NULL)
@@ -273,7 +312,7 @@ bus_policy_create_client_policy (BusPolicy      *policy,
               if (!add_list_to_client (list, client))
                 {
                   dbus_free (groups);
-                  goto failed;
+                  goto nomem;
                 }
             }
           
@@ -282,34 +321,55 @@ bus_policy_create_client_policy (BusPolicy      *policy,
 
       dbus_free (groups);
     }
-
-  if (!dbus_connection_get_unix_user (connection, &uid))
-    goto failed;
-
-  if (_dbus_hash_table_get_n_entries (policy->rules_by_uid) > 0)
+  
+  if (dbus_connection_get_unix_user (connection, &uid))
     {
-      DBusList **list;
-      
-      list = _dbus_hash_table_lookup_ulong (policy->rules_by_uid,
-                                            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;
+            }
+        }
 
-      if (list != NULL)
+      /* Add console rules */
+      at_console = _dbus_unix_user_is_at_console (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 (!add_list_to_client (list, client))
-            goto failed;
+          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 failed;
+    goto nomem;
 
   bus_client_policy_optimize (client);
   
   return client;
-  
+
+ nomem:
+  BUS_SET_OOM (error);
  failed:
-  bus_client_policy_unref (client);
+  _DBUS_ASSERT_ERROR_IS_SET (error);
+  if (client)
+    bus_client_policy_unref (client);
   return NULL;
 }
 
@@ -330,7 +390,7 @@ list_allows_user (dbus_bool_t           def,
     {
       BusPolicyRule *rule = link->data;
       link = _dbus_list_get_next_link (list, link);
-      
+
       if (rule->type == BUS_POLICY_RULE_USER)
         {
           _dbus_verbose ("List %p user rule uid="DBUS_UID_FORMAT"\n",
@@ -343,8 +403,8 @@ list_allows_user (dbus_bool_t           def,
         }
       else if (rule->type == BUS_POLICY_RULE_GROUP)
         {
-          _dbus_verbose ("List %p group rule uid="DBUS_UID_FORMAT"\n",
-                         list, rule->d.user.uid);
+          _dbus_verbose ("List %p group rule gid="DBUS_GID_FORMAT"\n",
+                         list, rule->d.group.gid);
           
           if (rule->d.group.gid == DBUS_GID_UNSET)
             ;  /* '*' wildcard */
@@ -374,24 +434,23 @@ list_allows_user (dbus_bool_t           def,
 }
 
 dbus_bool_t
-bus_policy_allow_user (BusPolicy        *policy,
-                       DBusUserDatabase *user_database,
-                       unsigned long     uid)
+bus_policy_allow_unix_user (BusPolicy        *policy,
+                            unsigned long     uid)
 {
   dbus_bool_t allowed;
   unsigned long *group_ids;
   int n_group_ids;
 
   /* On OOM or error we always reject the user */
-  if (!_dbus_user_database_get_groups (user_database,
-                                       uid, &group_ids, &n_group_ids, NULL))
+  if (!_dbus_unix_groups_from_uid (uid, &group_ids, &n_group_ids))
     {
       _dbus_verbose ("Did not get any groups for UID %lu\n",
                      uid);
       return FALSE;
     }
-  
-  allowed = FALSE;
+
+  /* Default to "user owning bus" can connect */
+  allowed = _dbus_unix_user_is_process_owner (uid);
 
   allowed = list_allows_user (allowed,
                               &policy->default_rules,
@@ -410,6 +469,23 @@ bus_policy_allow_user (BusPolicy        *policy,
   return allowed;
 }
 
+/* For now this is never actually called because the default
+ * DBusConnection behavior of 'same user that owns the bus can
+ * connect' is all it would do. Set the windows user function in
+ * connection.c if the config file ever supports doing something
+ * interesting here.
+ */
+dbus_bool_t
+bus_policy_allow_windows_user (BusPolicy        *policy,
+                               const char       *windows_sid)
+{
+  /* Windows has no policies here since only the session bus
+   * is really used for now, so just checking that the
+   * connecting person is the same as the bus owner is fine.
+   */
+  return _dbus_windows_user_is_process_owner (windows_sid);
+}
+
 dbus_bool_t
 bus_policy_append_default_rule (BusPolicy      *policy,
                                 BusPolicyRule  *rule)
@@ -434,13 +510,15 @@ bus_policy_append_mandatory_rule (BusPolicy      *policy,
   return TRUE;
 }
 
+
+
 static DBusList**
 get_list (DBusHashTable *hash,
           unsigned long  key)
 {
   DBusList **list;
 
-  list = _dbus_hash_table_lookup_ulong (hash, key);
+  list = _dbus_hash_table_lookup_uintptr (hash, key);
 
   if (list == NULL)
     {
@@ -448,7 +526,7 @@ get_list (DBusHashTable *hash,
       if (list == NULL)
         return NULL;
 
-      if (!_dbus_hash_table_insert_ulong (hash, key, list))
+      if (!_dbus_hash_table_insert_uintptr (hash, key, list))
         {
           dbus_free (list);
           return NULL;
@@ -498,6 +576,118 @@ bus_policy_append_group_rule (BusPolicy      *policy,
   return TRUE;
 }
 
+dbus_bool_t
+bus_policy_append_console_rule (BusPolicy      *policy,
+                                dbus_bool_t     at_console,
+                                BusPolicyRule  *rule)
+{
+  if (at_console)
+    {
+      if (!_dbus_list_append (&policy->at_console_true_rules, rule))
+        return FALSE;
+    }
+    else
+    {
+      if (!_dbus_list_append (&policy->at_console_false_rules, rule))
+        return FALSE;
+    }
+
+  bus_policy_rule_ref (rule);
+
+  return TRUE;
+
+}
+
+static dbus_bool_t
+append_copy_of_policy_list (DBusList **list,
+                            DBusList **to_append)
+{
+  DBusList *link;
+  DBusList *tmp_list;
+
+  tmp_list = NULL;
+
+  /* Preallocate all our links */
+  link = _dbus_list_get_first_link (to_append);
+  while (link != NULL)
+    {
+      if (!_dbus_list_append (&tmp_list, link->data))
+        {
+          _dbus_list_clear (&tmp_list);
+          return FALSE;
+        }
+      
+      link = _dbus_list_get_next_link (to_append, link);
+    }
+
+  /* Now append them */
+  while ((link = _dbus_list_pop_first_link (&tmp_list)))
+    {
+      bus_policy_rule_ref (link->data);
+      _dbus_list_append_link (list, link);
+    }
+
+  return TRUE;
+}
+
+static dbus_bool_t
+merge_id_hash (DBusHashTable *dest,
+               DBusHashTable *to_absorb)
+{
+  DBusHashIter iter;
+  
+  _dbus_hash_iter_init (to_absorb, &iter);
+  while (_dbus_hash_iter_next (&iter))
+    {
+      unsigned long id = _dbus_hash_iter_get_uintptr_key (&iter);
+      DBusList **list = _dbus_hash_iter_get_value (&iter);
+      DBusList **target = get_list (dest, id);
+
+      if (target == NULL)
+        return FALSE;
+
+      if (!append_copy_of_policy_list (target, list))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+dbus_bool_t
+bus_policy_merge (BusPolicy *policy,
+                  BusPolicy *to_absorb)
+{
+  /* FIXME Not properly atomic, but as used for configuration files we
+   * don't rely on it quite so much.
+   */
+  
+  if (!append_copy_of_policy_list (&policy->default_rules,
+                                   &to_absorb->default_rules))
+    return FALSE;
+  
+  if (!append_copy_of_policy_list (&policy->mandatory_rules,
+                                   &to_absorb->mandatory_rules))
+    return FALSE;
+
+  if (!append_copy_of_policy_list (&policy->at_console_true_rules,
+                                   &to_absorb->at_console_true_rules))
+    return FALSE;
+
+  if (!append_copy_of_policy_list (&policy->at_console_false_rules,
+                                   &to_absorb->at_console_false_rules))
+    return FALSE;
+
+  if (!merge_id_hash (policy->rules_by_uid,
+                      to_absorb->rules_by_uid))
+    return FALSE;
+  
+  if (!merge_id_hash (policy->rules_by_gid,
+                      to_absorb->rules_by_gid))
+    return FALSE;
+
+  return TRUE;
+}
+
 struct BusClientPolicy
 {
   int refcount;
@@ -519,12 +709,14 @@ bus_client_policy_new (void)
   return policy;
 }
 
-void
+BusClientPolicy *
 bus_client_policy_ref (BusClientPolicy *policy)
 {
   _dbus_assert (policy->refcount > 0);
 
   policy->refcount += 1;
+
+  return policy;
 }
 
 static void
@@ -550,7 +742,7 @@ bus_client_policy_unref (BusClientPolicy *policy)
                           NULL);
 
       _dbus_list_clear (&policy->rules);
-      
+
       dbus_free (policy);
     }
 }
@@ -585,8 +777,8 @@ bus_client_policy_optimize (BusClientPolicy *policy)
 
   /* The idea here is that if we have:
    * 
-   * <allow send="foo"/>
-   * <deny send="*"/>
+   * <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
@@ -618,12 +810,20 @@ bus_client_policy_optimize (BusClientPolicy *policy)
         {
         case BUS_POLICY_RULE_SEND:
           remove_preceding =
-            rule->d.send.message_name == NULL &&
+            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_name == NULL &&
+            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:
@@ -665,8 +865,11 @@ bus_client_policy_append_rule (BusClientPolicy *policy,
 dbus_bool_t
 bus_client_policy_check_can_send (BusClientPolicy *policy,
                                   BusRegistry     *registry,
+                                  dbus_bool_t      requested_reply,
                                   DBusConnection  *receiver,
-                                  DBusMessage     *message)
+                                  DBusMessage     *message,
+                                  dbus_int32_t    *toggles,
+                                  dbus_bool_t     *log)
 {
   DBusList *link;
   dbus_bool_t allowed;
@@ -675,6 +878,9 @@ bus_client_policy_check_can_send (BusClientPolicy *policy,
    * in the config file, i.e. last rule that applies wins
    */
 
+  _dbus_verbose ("  (policy) checking send rules\n");
+  *toggles = 0;
+  
   allowed = FALSE;
   link = _dbus_list_get_first_link (&policy->rules);
   while (link != NULL)
@@ -689,15 +895,98 @@ bus_client_policy_check_can_send (BusClientPolicy *policy,
        */
       
       if (rule->type != BUS_POLICY_RULE_SEND)
-        continue;
+        {
+          _dbus_verbose ("  (policy) skipping non-send rule\n");
+          continue;
+        }
 
-      if (rule->d.send.message_name != NULL)
+      if (rule->d.send.message_type != DBUS_MESSAGE_TYPE_INVALID)
         {
-          if (!dbus_message_has_name (message,
-                                      rule->d.send.message_name))
-            continue;
+          if (dbus_message_get_type (message) != rule->d.send.message_type)
+            {
+              _dbus_verbose ("  (policy) skipping rule for different message type\n");
+              continue;
+            }
         }
 
+      /* If it's a reply, the requested_reply flag kicks in */
+      if (dbus_message_get_reply_serial (message) != 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.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;
+            }
+        }
+      
+      if (rule->d.send.path != NULL)
+        {
+          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;
+            }
+        }
+      
+      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).
+           */
+          dbus_bool_t no_interface;
+
+          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 (message) != NULL &&
+              strcmp (dbus_message_get_member (message),
+                      rule->d.send.member) != 0)
+            {
+              _dbus_verbose ("  (policy) skipping rule for different member\n");
+              continue;
+            }
+        }
+
+      if (rule->d.send.error != NULL)
+        {
+          if (dbus_message_get_error_name (message) != NULL &&
+              strcmp (dbus_message_get_error_name (message),
+                      rule->d.send.error) != 0)
+            {
+              _dbus_verbose ("  (policy) skipping rule for different error name\n");
+              continue;
+            }
+        }
+      
       if (rule->d.send.destination != NULL)
         {
           /* receiver can be NULL for messages that are sent to the
@@ -707,9 +996,13 @@ bus_client_policy_check_can_send (BusClientPolicy *policy,
            */
           if (receiver == NULL)
             {
-              if (!dbus_message_has_sender (message,
-                                            rule->d.send.destination))
-                continue;
+              if (!dbus_message_has_destination (message,
+                                                 rule->d.send.destination))
+                {
+                  _dbus_verbose ("  (policy) skipping rule because message dest is not %s\n",
+                                 rule->d.send.destination);
+                  continue;
+                }
             }
           else
             {
@@ -720,56 +1013,180 @@ bus_client_policy_check_can_send (BusClientPolicy *policy,
               
               service = bus_registry_lookup (registry, &str);
               if (service == NULL)
-                continue;
+                {
+                  _dbus_verbose ("  (policy) skipping rule because dest %s doesn't exist\n",
+                                 rule->d.send.destination);
+                  continue;
+                }
 
               if (!bus_service_has_owner (service, receiver))
-                continue;
+                {
+                  _dbus_verbose ("  (policy) skipping rule because dest %s isn't owned by receiver\n",
+                                 rule->d.send.destination);
+                  continue;
+                }
             }
         }
 
       /* Use this rule */
       allowed = rule->allow;
+      *log = rule->d.send.log;
+      (*toggles)++;
+
+      _dbus_verbose ("  (policy) used rule, allow now = %d\n",
+                     allowed);
     }
 
   return allowed;
 }
 
+/* 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,
-                                     DBusMessage     *message)
+                                     DBusConnection  *addressed_recipient,
+                                     DBusConnection  *proposed_recipient,
+                                     DBusMessage     *message,
+                                     dbus_int32_t    *toggles)
 {
   DBusList *link;
   dbus_bool_t allowed;
+  dbus_bool_t eavesdropping;
+
+  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);
   while (link != NULL)
     {
       BusPolicyRule *rule = link->data;
 
-      link = _dbus_list_get_next_link (&policy->rules, link);
+      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
-       * origin from the message
+      if (rule->type != BUS_POLICY_RULE_RECEIVE)
+        {
+          _dbus_verbose ("  (policy) skipping non-receive rule\n");
+          continue;
+        }
+
+      if (rule->d.receive.message_type != DBUS_MESSAGE_TYPE_INVALID)
+        {
+          if (dbus_message_get_type (message) != rule->d.receive.message_type)
+            {
+              _dbus_verbose ("  (policy) skipping rule for different message type\n");
+              continue;
+            }
+        }
+
+      /* for allow, eavesdrop=false means the rule doesn't apply when
+       * eavesdropping. eavesdrop=true means always allow.
+       */
+      if (eavesdropping && rule->allow && !rule->d.receive.eavesdrop)
+        {
+          _dbus_verbose ("  (policy) skipping allow rule since it doesn't apply to eavesdropping\n");
+          continue;
+        }
+
+      /* for deny, eavesdrop=true means the rule applies only when
+       * eavesdropping; eavesdrop=false means always deny.
        */
+      if (!eavesdropping && !rule->allow && rule->d.receive.eavesdrop)
+        {
+          _dbus_verbose ("  (policy) skipping deny rule since it only applies to eavesdropping\n");
+          continue;
+        }
+
+      /* If it's a reply, the requested_reply flag kicks in */
+      if (dbus_message_get_reply_serial (message) != 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;
+            }
+
+          /* 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->type != BUS_POLICY_RULE_RECEIVE)
-        continue;
+      if (rule->d.receive.path != NULL)
+        {
+          if (dbus_message_get_path (message) != NULL &&
+              strcmp (dbus_message_get_path (message),
+                      rule->d.receive.path) != 0)
+            {
+              _dbus_verbose ("  (policy) skipping rule for different path\n");
+              continue;
+            }
+        }
+      
+      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).
+           */
+          dbus_bool_t no_interface;
+
+          no_interface = dbus_message_get_interface (message) == NULL;
+          
+          if ((no_interface && rule->allow) ||
+              (!no_interface &&
+               strcmp (dbus_message_get_interface (message),
+                       rule->d.receive.interface) != 0))
+            {
+              _dbus_verbose ("  (policy) skipping rule for different interface\n");
+              continue;
+            }
+        }      
 
-      if (rule->d.receive.message_name != NULL)
+      if (rule->d.receive.member != NULL)
         {
-          if (!dbus_message_has_name (message,
-                                      rule->d.receive.message_name))
-            continue;
+          if (dbus_message_get_member (message) != NULL &&
+              strcmp (dbus_message_get_member (message),
+                      rule->d.receive.member) != 0)
+            {
+              _dbus_verbose ("  (policy) skipping rule for different member\n");
+              continue;
+            }
         }
 
+      if (rule->d.receive.error != NULL)
+        {
+          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;
+            }
+        }
+      
       if (rule->d.receive.origin != NULL)
         {          
           /* sender can be NULL for messages that originate from the
@@ -781,7 +1198,11 @@ bus_client_policy_check_can_receive (BusClientPolicy *policy,
             {
               if (!dbus_message_has_sender (message,
                                             rule->d.receive.origin))
-                continue;
+                {
+                  _dbus_verbose ("  (policy) skipping rule because message sender is not %s\n",
+                                 rule->d.receive.origin);
+                  continue;
+                }
             }
           else
             {
@@ -793,39 +1214,52 @@ bus_client_policy_check_can_receive (BusClientPolicy *policy,
               service = bus_registry_lookup (registry, &str);
               
               if (service == NULL)
-                continue;
+                {
+                  _dbus_verbose ("  (policy) skipping rule because origin %s doesn't exist\n",
+                                 rule->d.receive.origin);
+                  continue;
+                }
 
               if (!bus_service_has_owner (service, sender))
-                continue;
+                {
+                  _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)++;
+
+      _dbus_verbose ("  (policy) used rule, allow now = %d\n",
+                     allowed);
     }
 
   return allowed;
 }
 
-dbus_bool_t
-bus_client_policy_check_can_own (BusClientPolicy  *policy,
-                                 DBusConnection   *connection,
-                                 const DBusString *service_name)
+
+
+static dbus_bool_t
+bus_rules_check_can_own (DBusList *rules,
+                         const DBusString *service_name)
 {
   DBusList *link;
   dbus_bool_t allowed;
   
-  /* policy->rules is in the order the rules appeared
+  /* rules is in the order the rules appeared
    * in the config file, i.e. last rule that applies wins
    */
 
   allowed = FALSE;
-  link = _dbus_list_get_first_link (&policy->rules);
+  link = _dbus_list_get_first_link (&rules);
   while (link != NULL)
     {
       BusPolicyRule *rule = link->data;
 
-      link = _dbus_list_get_next_link (&policy->rules, link);
+      link = _dbus_list_get_next_link (&rules, link);
       
       /* Rule is skipped if it specifies a different service name from
        * the desired one.
@@ -834,12 +1268,25 @@ bus_client_policy_check_can_own (BusClientPolicy  *policy,
       if (rule->type != BUS_POLICY_RULE_OWN)
         continue;
 
-      if (rule->d.own.service_name != NULL)
+      if (!rule->d.own.prefix && rule->d.own.service_name != NULL)
         {
           if (!_dbus_string_equal_c_str (service_name,
                                          rule->d.own.service_name))
             continue;
         }
+      else if (rule->d.own.prefix)
+        {
+          const char *data;
+          char next_char;
+          if (!_dbus_string_starts_with_c_str (service_name,
+                                               rule->d.own.service_name))
+            continue;
+
+          data = _dbus_string_get_const_data (service_name);
+          next_char = data[strlen (rule->d.own.service_name)];
+          if (next_char != '\0' && next_char != '.')
+            continue;
+        }
 
       /* Use this rule */
       allowed = rule->allow;
@@ -848,17 +1295,19 @@ bus_client_policy_check_can_own (BusClientPolicy  *policy,
   return allowed;
 }
 
-#ifdef DBUS_BUILD_TESTS
+dbus_bool_t
+bus_client_policy_check_can_own (BusClientPolicy  *policy,
+                                 const DBusString *service_name)
+{
+  return bus_rules_check_can_own (policy->rules, service_name);
+}
 
+#ifdef DBUS_ENABLE_EMBEDDED_TESTS
 dbus_bool_t
-bus_policy_test (const DBusString *test_data_dir)
+bus_policy_check_can_own (BusPolicy  *policy,
+                          const DBusString *service_name)
 {
-  /* This doesn't do anything for now because I decided to do it in
-   * dispatch.c instead by having some of the clients in dispatch.c
-   * have particular policies applied to them.
-   */
-  
-  return TRUE;
+  return bus_rules_check_can_own (policy->default_rules, service_name);
 }
+#endif /* DBUS_ENABLE_EMBEDDED_TESTS */
 
-#endif /* DBUS_BUILD_TESTS */