2004-07-24 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / bus / policy.c
index 74ed710..c7359c8 100644 (file)
@@ -1,9 +1,9 @@
 /* -*- mode: C; c-file-style: "gnu" -*- */
 /* 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.0
  * 
  * 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
@@ -52,7 +52,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;
     }
@@ -60,12 +73,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
@@ -80,11 +95,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:
@@ -124,6 +145,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);
@@ -153,7 +177,7 @@ bus_policy_new (void)
                                                free_rule_list_func);
   if (policy->rules_by_gid == NULL)
     goto failed;
-  
+
   return policy;
   
  failed:
@@ -161,12 +185,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
@@ -509,6 +535,88 @@ bus_policy_append_group_rule (BusPolicy      *policy,
   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_ulong_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 (!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;
@@ -530,12 +638,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
@@ -561,7 +671,7 @@ bus_client_policy_unref (BusClientPolicy *policy)
                           NULL);
 
       _dbus_list_clear (&policy->rules);
-      
+
       dbus_free (policy);
     }
 }
@@ -596,8 +706,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
@@ -629,12 +739,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:
@@ -676,6 +794,7 @@ 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)
 {
@@ -707,16 +826,83 @@ bus_client_policy_check_can_send (BusClientPolicy *policy,
           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))
+          if (dbus_message_get_type (message) != rule->d.send.message_type)
             {
-              _dbus_verbose ("  (policy) skipping rule for different message name\n");
+              _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)
+            {
+              _dbus_verbose ("  (policy) skipping allow rule since it only applies to requested replies\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)
+        {
+          if (dbus_message_get_interface (message) != NULL &&
+              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
@@ -768,20 +954,31 @@ bus_client_policy_check_can_send (BusClientPolicy *policy,
   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,
+                                     DBusConnection  *addressed_recipient,
+                                     DBusConnection  *proposed_recipient,
                                      DBusMessage     *message)
 {
   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\n");
+  _dbus_verbose ("  (policy) checking receive rules, eavesdropping = %d\n", eavesdropping);
   
   allowed = FALSE;
   link = _dbus_list_get_first_link (&policy->rules);
@@ -789,12 +986,7 @@ bus_client_policy_check_can_receive (BusClientPolicy *policy,
     {
       BusPolicyRule *rule = link->data;
 
-      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
-       */
+      link = _dbus_list_get_next_link (&policy->rules, link);      
       
       if (rule->type != BUS_POLICY_RULE_RECEIVE)
         {
@@ -802,16 +994,101 @@ bus_client_policy_check_can_receive (BusClientPolicy *policy,
           continue;
         }
 
-      if (rule->d.receive.message_name != NULL)
+      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)
+            {
+              _dbus_verbose ("  (policy) skipping allow rule since it only applies to requested replies\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->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)
+        {
+          if (dbus_message_get_interface (message) != NULL &&
+              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.member != NULL)
         {
-          if (!dbus_message_has_name (message,
-                                      rule->d.receive.message_name))
+          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 message name\n");
+              _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
@@ -853,7 +1130,7 @@ bus_client_policy_check_can_receive (BusClientPolicy *policy,
                 }
             }
         }
-
+      
       /* Use this rule */
       allowed = rule->allow;