2004-07-24 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / bus / config-parser.c
index bd1c47b..29fade1 100644 (file)
@@ -1,9 +1,9 @@
 /* -*- mode: C; c-file-style: "gnu" -*- */
 /* config-parser.c  XML-library-agnostic configuration file parser
  *
- * 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
@@ -24,6 +24,7 @@
 #include "test.h"
 #include "utils.h"
 #include "policy.h"
+#include "selinux.h"
 #include <dbus/dbus-list.h>
 #include <dbus/dbus-internals.h>
 #include <string.h>
@@ -44,7 +45,9 @@ typedef enum
   ELEMENT_PIDFILE,
   ELEMENT_SERVICEDIR,
   ELEMENT_INCLUDEDIR,
-  ELEMENT_TYPE
+  ELEMENT_TYPE,
+  ELEMENT_SELINUX,
+  ELEMENT_ASSOCIATE
 } ElementType;
 
 typedef enum
@@ -88,9 +91,12 @@ typedef struct
 
 } Element;
 
+/**
+ * Parser for bus configuration file. 
+ */
 struct BusConfigParser
 {
-  int refcount;
+  int refcount;        /**< Reference count */
 
   DBusString basedir;  /**< Directory we resolve paths relative to */
   
@@ -109,10 +115,16 @@ struct BusConfigParser
   BusPolicy *policy;     /**< Security policy */
 
   BusLimits limits;      /**< Limits */
-  
+
+  char *pidfile;         /**< PID file */
+
+  DBusList *included_files;  /**< Included files stack */
+
+  DBusHashTable *service_sid_table; /**< Map service names to SELinux contexts */
+
   unsigned int fork : 1; /**< TRUE to fork into daemon mode */
 
-  char *pidfile;
+  unsigned int is_toplevel : 1; /**< FALSE if we are a sub-config-file inside another one */
 };
 
 static const char*
@@ -150,6 +162,10 @@ element_type_to_name (ElementType type)
       return "includedir";
     case ELEMENT_TYPE:
       return "type";
+    case ELEMENT_SELINUX:
+      return "selinux";
+    case ELEMENT_ASSOCIATE:
+      return "associate";
     }
 
   _dbus_assert_not_reached ("bad element type");
@@ -228,7 +244,26 @@ merge_included (BusConfigParser *parser,
                 DBusError       *error)
 {
   DBusList *link;
+  DBusHashTable *table;
+
+  if (!bus_policy_merge (parser->policy,
+                         included->policy))
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  table = bus_selinux_id_table_union (parser->service_sid_table,
+                                      included->service_sid_table);
+  if (table == NULL)
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
 
+  _dbus_hash_table_unref (parser->service_sid_table);
+  parser->service_sid_table = table;
+  
   if (included->user != NULL)
     {
       dbus_free (parser->user);
@@ -265,8 +300,28 @@ merge_included (BusConfigParser *parser,
   return TRUE;
 }
 
+static dbus_bool_t
+seen_include (BusConfigParser  *parser,
+             const DBusString *file)
+{
+  DBusList *iter;
+
+  iter = parser->included_files;
+  while (iter != NULL)
+    {
+      if (! strcmp (_dbus_string_get_const_data (file), iter->data))
+       return TRUE;
+
+      iter = _dbus_list_get_next_link (&parser->included_files, iter);
+    }
+
+  return FALSE;
+}
+
 BusConfigParser*
-bus_config_parser_new (const DBusString *basedir)
+bus_config_parser_new (const DBusString      *basedir,
+                       dbus_bool_t            is_toplevel,
+                       const BusConfigParser *parent)
 {
   BusConfigParser *parser;
 
@@ -274,6 +329,8 @@ bus_config_parser_new (const DBusString *basedir)
   if (parser == NULL)
     return NULL;
 
+  parser->is_toplevel = !!is_toplevel;
+  
   if (!_dbus_string_init (&parser->basedir))
     {
       dbus_free (parser);
@@ -281,53 +338,81 @@ bus_config_parser_new (const DBusString *basedir)
     }
 
   if (((parser->policy = bus_policy_new ()) == NULL) ||
-      !_dbus_string_copy (basedir, 0, &parser->basedir, 0))
+      !_dbus_string_copy (basedir, 0, &parser->basedir, 0) ||
+      ((parser->service_sid_table = bus_selinux_id_table_new ()) == NULL))
     {
       if (parser->policy)
         bus_policy_unref (parser->policy);
       
       _dbus_string_free (&parser->basedir);
+
+      if (parser->service_sid_table == NULL)
+        _dbus_hash_table_unref (parser->service_sid_table);
+      
       dbus_free (parser);
       return NULL;
     }
 
-  /* Make up some numbers! woot! */
-  parser->limits.max_incoming_bytes = _DBUS_ONE_MEGABYTE * 63;
-  parser->limits.max_outgoing_bytes = _DBUS_ONE_MEGABYTE * 63;
-  parser->limits.max_message_size = _DBUS_ONE_MEGABYTE * 32;
-  
-#ifdef DBUS_BUILD_TESTS
-  parser->limits.activation_timeout = 6000;  /* 6 seconds */
-#else
-  parser->limits.activation_timeout = 15000; /* 15 seconds */
-#endif
+  if (parent != NULL)
+    {
+      /* Initialize the parser's limits from the parent. */
+      parser->limits = parent->limits;
 
-  /* Making this long risks making a DOS attack easier, but too short
-   * and legitimate auth will fail.  If interactive auth (ask user for
-   * password) is allowed, then potentially it has to be quite long.
-   */     
-  parser->limits.auth_timeout = 3000; /* 3 seconds */
+      /* Use the parent's list of included_files to avoid
+        circular inclusions. */
+      parser->included_files = parent->included_files;
+    }
+  else
+    {
 
-  parser->limits.max_incomplete_connections = 32;
-  parser->limits.max_connections_per_user = 128;
+      /* Make up some numbers! woot! */
+      parser->limits.max_incoming_bytes = _DBUS_ONE_MEGABYTE * 63;
+      parser->limits.max_outgoing_bytes = _DBUS_ONE_MEGABYTE * 63;
+      parser->limits.max_message_size = _DBUS_ONE_MEGABYTE * 32;
+      
+      /* Making this long means the user has to wait longer for an error
+       * message if something screws up, but making it too short means
+       * they might see a false failure.
+       */
+      parser->limits.activation_timeout = 25000; /* 25 seconds */
 
-  /* Note that max_completed_connections / max_connections_per_user
-   * is the number of users that would have to work together to
-   * DOS all the other users.
-   */
-  parser->limits.max_completed_connections = 1024;
-  
+      /* Making this long risks making a DOS attack easier, but too short
+       * and legitimate auth will fail.  If interactive auth (ask user for
+       * password) is allowed, then potentially it has to be quite long.
+       */
+      parser->limits.auth_timeout = 30000; /* 30 seconds */
+      
+      parser->limits.max_incomplete_connections = 32;
+      parser->limits.max_connections_per_user = 128;
+      
+      /* Note that max_completed_connections / max_connections_per_user
+       * is the number of users that would have to work together to
+       * DOS all the other users.
+       */
+      parser->limits.max_completed_connections = 1024;
+      
+      parser->limits.max_pending_activations = 256;
+      parser->limits.max_services_per_connection = 256;
+      
+      parser->limits.max_match_rules_per_connection = 128;
+      
+      parser->limits.reply_timeout = 5 * 60 * 1000; /* 5 minutes */
+      parser->limits.max_replies_per_connection = 32;
+    }
+      
   parser->refcount = 1;
-
+      
   return parser;
 }
 
-void
+BusConfigParser *
 bus_config_parser_ref (BusConfigParser *parser)
 {
   _dbus_assert (parser->refcount > 0);
 
   parser->refcount += 1;
+
+  return parser;
 }
 
 void
@@ -368,6 +453,9 @@ bus_config_parser_unref (BusConfigParser *parser)
 
       if (parser->policy)
         bus_policy_unref (parser->policy);
+
+      if (parser->service_sid_table)
+        _dbus_hash_table_unref (parser->service_sid_table);
       
       dbus_free (parser);
     }
@@ -599,7 +687,7 @@ start_busconfig_child (BusConfigParser   *parser,
           BUS_SET_OOM (error);
           return FALSE;
         }
-
+      
       return TRUE;
     }
   else if (strcmp (element_name, "includedir") == 0)
@@ -784,6 +872,22 @@ start_busconfig_child (BusConfigParser   *parser,
 
       return TRUE;
     }
+  else if (strcmp (element_name, "selinux") == 0)
+    {
+      Element *e;
+      const char *name;
+
+      if (!check_no_attributes (parser, "selinux", attribute_names, attribute_values, error))
+        return FALSE;
+
+      if (push_element (parser, ELEMENT_SELINUX) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      return TRUE;
+    }
   else
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -801,11 +905,22 @@ append_rule_from_element (BusConfigParser   *parser,
                           dbus_bool_t        allow,
                           DBusError         *error)
 {
-  const char *send;
-  const char *receive;
+  const char *send_interface;
+  const char *send_member;
+  const char *send_error;
+  const char *send_destination;
+  const char *send_path;
+  const char *send_type;
+  const char *receive_interface;
+  const char *receive_member;
+  const char *receive_error;
+  const char *receive_sender;
+  const char *receive_path;
+  const char *receive_type;
+  const char *eavesdrop;
+  const char *send_requested_reply;
+  const char *receive_requested_reply;
   const char *own;
-  const char *send_to;
-  const char *receive_from;
   const char *user;
   const char *group;
   BusPolicyRule *rule;
@@ -814,57 +929,170 @@ append_rule_from_element (BusConfigParser   *parser,
                           attribute_names,
                           attribute_values,
                           error,
-                          "send", &send,
-                          "receive", &receive,
+                          "send_interface", &send_interface,
+                          "send_member", &send_member,
+                          "send_error", &send_error,
+                          "send_destination", &send_destination,
+                          "send_path", &send_path,
+                          "send_type", &send_type,
+                          "receive_interface", &receive_interface,
+                          "receive_member", &receive_member,
+                          "receive_error", &receive_error,
+                          "receive_sender", &receive_sender,
+                          "receive_path", &receive_path,
+                          "receive_type", &receive_type,
+                          "eavesdrop", &eavesdrop,
+                          "send_requested_reply", &send_requested_reply,
+                          "receive_requested_reply", &receive_requested_reply,
                           "own", &own,
-                          "send_to", &send_to,
-                          "receive_from", &receive_from,
                           "user", &user,
                           "group", &group,
                           NULL))
     return FALSE;
 
-  if (!(send || receive || own || send_to || receive_from ||
-        user || group))
+  if (!(send_interface || send_member || send_error || send_destination ||
+        send_type || send_path ||
+        receive_interface || receive_member || receive_error || receive_sender ||
+        receive_type || receive_path || eavesdrop ||
+        send_requested_reply || receive_requested_reply ||
+        own || user || group))
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
                       "Element <%s> must have one or more attributes",
                       element_name);
       return FALSE;
     }
+
+  if ((send_member && (send_interface == NULL && send_path == NULL)) ||
+      (receive_member && (receive_interface == NULL && receive_path == NULL)))
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "On element <%s>, if you specify a member you must specify an interface or a path. Keep in mind that not all messages have an interface field.",
+                      element_name);
+      return FALSE;
+    }
   
-  if (((send && own) ||
-       (send && receive) ||
-       (send && receive_from) ||
-       (send && user) ||
-       (send && group)) ||
-
-      ((receive && own) ||
-       (receive && send_to) ||
-       (receive && user) ||
-       (receive && group)) ||
-
-      ((own && send_to) ||
-       (own && receive_from) ||
-       (own && user) ||
-       (own && group)) ||
+  /* Allowed combinations of elements are:
+   *
+   *   base, must be all send or all receive:
+   *     nothing
+   *     interface
+   *     interface + member
+   *     error
+   * 
+   *   base send_ can combine with send_destination, send_path, send_type, send_requested_reply
+   *   base receive_ with receive_sender, receive_path, receive_type, receive_requested_reply, eavesdrop
+   *
+   *   user, group, own must occur alone
+   *
+   * Pretty sure the below stuff is broken, FIXME think about it more.
+   */
 
-      ((send_to && receive_from) ||
-       (send_to && user) ||
-       (send_to && group)) ||
+  if (((send_interface && send_error) ||
+       (send_interface && receive_interface) ||
+       (send_interface && receive_member) ||
+       (send_interface && receive_error) ||
+       (send_interface && receive_sender) ||
+       (send_interface && eavesdrop) ||
+       (send_interface && receive_requested_reply) ||
+       (send_interface && own) ||
+       (send_interface && user) ||
+       (send_interface && group)) ||
+
+      ((send_member && send_error) ||
+       (send_member && receive_interface) ||
+       (send_member && receive_member) ||
+       (send_member && receive_error) ||
+       (send_member && receive_sender) ||
+       (send_member && eavesdrop) ||
+       (send_member && receive_requested_reply) ||
+       (send_member && own) ||
+       (send_member && user) ||
+       (send_member && group)) ||
+      
+      ((send_error && receive_interface) ||
+       (send_error && receive_member) ||
+       (send_error && receive_error) ||
+       (send_error && receive_sender) ||
+       (send_error && eavesdrop) ||
+       (send_error && receive_requested_reply) ||
+       (send_error && own) ||
+       (send_error && user) ||
+       (send_error && group)) ||
+
+      ((send_destination && receive_interface) ||
+       (send_destination && receive_member) ||
+       (send_destination && receive_error) ||
+       (send_destination && receive_sender) ||
+       (send_destination && eavesdrop) ||
+       (send_destination && receive_requested_reply) ||
+       (send_destination && own) ||
+       (send_destination && user) ||
+       (send_destination && group)) ||
+
+      ((send_type && receive_interface) ||
+       (send_type && receive_member) ||
+       (send_type && receive_error) ||
+       (send_type && receive_sender) ||
+       (send_type && eavesdrop) ||
+       (send_type && receive_requested_reply) ||
+       (send_type && own) ||
+       (send_type && user) ||
+       (send_type && group)) ||
+
+      ((send_path && receive_interface) ||
+       (send_path && receive_member) ||
+       (send_path && receive_error) ||
+       (send_path && receive_sender) ||
+       (send_path && eavesdrop) ||
+       (send_path && receive_requested_reply) ||
+       (send_path && own) ||
+       (send_path && user) ||
+       (send_path && group)) ||
+
+      ((send_requested_reply && receive_interface) ||
+       (send_requested_reply && receive_member) ||
+       (send_requested_reply && receive_error) ||
+       (send_requested_reply && receive_sender) ||
+       (send_requested_reply && eavesdrop) ||
+       (send_requested_reply && receive_requested_reply) ||
+       (send_requested_reply && own) ||
+       (send_requested_reply && user) ||
+       (send_requested_reply && group)) ||
+      
+      ((receive_interface && receive_error) ||
+       (receive_interface && own) ||
+       (receive_interface && user) ||
+       (receive_interface && group)) ||
+
+      ((receive_member && receive_error) ||
+       (receive_member && own) ||
+       (receive_member && user) ||
+       (receive_member && group)) ||
+      
+      ((receive_error && own) ||
+       (receive_error && user) ||
+       (receive_error && group)) ||
 
-      ((receive_from && user) ||
-       (receive_from && group)) ||
+      ((eavesdrop && own) ||
+       (eavesdrop && user) ||
+       (eavesdrop && group)) ||
+
+      ((receive_requested_reply && own) ||
+       (receive_requested_reply && user) ||
+       (receive_requested_reply && group)) ||
+      
+      ((own && user) ||
+       (own && group)) ||
 
-      (user && group))
+      ((user && group)))
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
-                      "Invalid combination of attributes on element <%s>, "
-                      "only send/send_to or receive/receive_from may be paired",
+                      "Invalid combination of attributes on element <%s>",
                       element_name);
       return FALSE;
     }
-
+  
   rule = NULL;
 
   /* In BusPolicyRule, NULL represents wildcard.
@@ -872,41 +1100,149 @@ append_rule_from_element (BusConfigParser   *parser,
    */
 #define IS_WILDCARD(str) ((str) && ((str)[0]) == '*' && ((str)[1]) == '\0')
 
-  if (send || send_to)
+  if (send_interface || send_member || send_error || send_destination ||
+      send_path || send_type || send_requested_reply)
     {
+      int message_type;
+      
+      if (IS_WILDCARD (send_interface))
+        send_interface = NULL;
+      if (IS_WILDCARD (send_member))
+        send_member = NULL;
+      if (IS_WILDCARD (send_error))
+        send_error = NULL;
+      if (IS_WILDCARD (send_destination))
+        send_destination = NULL;
+      if (IS_WILDCARD (send_path))
+        send_path = NULL;
+      if (IS_WILDCARD (send_type))
+        send_type = NULL;
+
+      message_type = DBUS_MESSAGE_TYPE_INVALID;
+      if (send_type != NULL)
+        {
+          message_type = dbus_message_type_from_string (send_type);
+          if (message_type == DBUS_MESSAGE_TYPE_INVALID)
+            {
+              dbus_set_error (error, DBUS_ERROR_FAILED,
+                              "Bad message type \"%s\"",
+                              send_type);
+              return FALSE;
+            }
+        }
+
+      if (send_requested_reply &&
+          !(strcmp (send_requested_reply, "true") == 0 ||
+            strcmp (send_requested_reply, "false") == 0))
+        {
+          dbus_set_error (error, DBUS_ERROR_FAILED,
+                          "Bad value \"%s\" for %s attribute, must be true or false",
+                          "send_requested_reply", send_requested_reply);
+          return FALSE;
+        }
+      
       rule = bus_policy_rule_new (BUS_POLICY_RULE_SEND, allow); 
       if (rule == NULL)
         goto nomem;
-
-      if (IS_WILDCARD (send))
-        send = NULL;
-      if (IS_WILDCARD (send_to))
-        send_to = NULL;
       
-      rule->d.send.message_name = _dbus_strdup (send);
-      rule->d.send.destination = _dbus_strdup (send_to);
-      if (send && rule->d.send.message_name == NULL)
+      if (send_requested_reply)
+        rule->d.send.requested_reply = (strcmp (send_requested_reply, "true") == 0);
+      
+      rule->d.send.message_type = message_type;
+      rule->d.send.path = _dbus_strdup (send_path);
+      rule->d.send.interface = _dbus_strdup (send_interface);
+      rule->d.send.member = _dbus_strdup (send_member);
+      rule->d.send.error = _dbus_strdup (send_error);
+      rule->d.send.destination = _dbus_strdup (send_destination);
+      if (send_path && rule->d.send.path == NULL)
+        goto nomem;
+      if (send_interface && rule->d.send.interface == NULL)
+        goto nomem;
+      if (send_member && rule->d.send.member == NULL)
         goto nomem;
-      if (send_to && rule->d.send.destination == NULL)
+      if (send_error && rule->d.send.error == NULL)
+        goto nomem;
+      if (send_destination && rule->d.send.destination == NULL)
         goto nomem;
     }
-  else if (receive || receive_from)
+  else if (receive_interface || receive_member || receive_error || receive_sender ||
+           receive_path || receive_type || eavesdrop || receive_requested_reply)
     {
+      int message_type;
+      
+      if (IS_WILDCARD (receive_interface))
+        receive_interface = NULL;
+      if (IS_WILDCARD (receive_member))
+        receive_member = NULL;
+      if (IS_WILDCARD (receive_error))
+        receive_error = NULL;
+      if (IS_WILDCARD (receive_sender))
+        receive_sender = NULL;
+      if (IS_WILDCARD (receive_path))
+        receive_path = NULL;
+      if (IS_WILDCARD (receive_type))
+        receive_type = NULL;
+
+      message_type = DBUS_MESSAGE_TYPE_INVALID;
+      if (receive_type != NULL)
+        {
+          message_type = dbus_message_type_from_string (receive_type);
+          if (message_type == DBUS_MESSAGE_TYPE_INVALID)
+            {
+              dbus_set_error (error, DBUS_ERROR_FAILED,
+                              "Bad message type \"%s\"",
+                              receive_type);
+              return FALSE;
+            }
+        }
+
+
+      if (eavesdrop &&
+          !(strcmp (eavesdrop, "true") == 0 ||
+            strcmp (eavesdrop, "false") == 0))
+        {
+          dbus_set_error (error, DBUS_ERROR_FAILED,
+                          "Bad value \"%s\" for %s attribute, must be true or false",
+                          "eavesdrop", eavesdrop);
+          return FALSE;
+        }
+
+      if (receive_requested_reply &&
+          !(strcmp (receive_requested_reply, "true") == 0 ||
+            strcmp (receive_requested_reply, "false") == 0))
+        {
+          dbus_set_error (error, DBUS_ERROR_FAILED,
+                          "Bad value \"%s\" for %s attribute, must be true or false",
+                          "receive_requested_reply", receive_requested_reply);
+          return FALSE;
+        }
+      
       rule = bus_policy_rule_new (BUS_POLICY_RULE_RECEIVE, allow); 
       if (rule == NULL)
         goto nomem;
 
-      if (IS_WILDCARD (receive))
-        receive = NULL;
+      if (eavesdrop)
+        rule->d.receive.eavesdrop = (strcmp (eavesdrop, "true") == 0);
 
-      if (IS_WILDCARD (receive_from))
-        receive_from = NULL;
+      if (receive_requested_reply)
+        rule->d.receive.requested_reply = (strcmp (receive_requested_reply, "true") == 0);
       
-      rule->d.receive.message_name = _dbus_strdup (receive);
-      rule->d.receive.origin = _dbus_strdup (receive_from);
-      if (receive && rule->d.receive.message_name == NULL)
+      rule->d.receive.message_type = message_type;
+      rule->d.receive.path = _dbus_strdup (receive_path);
+      rule->d.receive.interface = _dbus_strdup (receive_interface);
+      rule->d.receive.member = _dbus_strdup (receive_member);
+      rule->d.receive.error = _dbus_strdup (receive_error);
+      rule->d.receive.origin = _dbus_strdup (receive_sender);
+
+      if (receive_path && rule->d.receive.path == NULL)
         goto nomem;
-      if (receive_from && rule->d.receive.origin == NULL)
+      if (receive_interface && rule->d.receive.interface == NULL)
+        goto nomem;
+      if (receive_member && rule->d.receive.member == NULL)
+        goto nomem;
+      if (receive_error && rule->d.receive.error == NULL)
+        goto nomem;
+      if (receive_sender && rule->d.receive.origin == NULL)
         goto nomem;
     }
   else if (own)
@@ -1099,6 +1435,58 @@ start_policy_child (BusConfigParser   *parser,
     }
 }
 
+static dbus_bool_t
+start_selinux_child (BusConfigParser   *parser,
+                     const char        *element_name,
+                     const char       **attribute_names,
+                     const char       **attribute_values,
+                     DBusError         *error)
+{
+  if (strcmp (element_name, "associate") == 0)
+    {
+      const char *own;
+      const char *context;
+      
+      if (!locate_attributes (parser, "associate",
+                              attribute_names,
+                              attribute_values,
+                              error,
+                              "own", &own,
+                              "context", &context,
+                              NULL))
+        return FALSE;
+      
+      if (push_element (parser, ELEMENT_ASSOCIATE) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      if (own == NULL || context == NULL)
+        {
+          dbus_set_error (error, DBUS_ERROR_FAILED,
+                          "Element <associate> must have attributes own=\"<servicename>\" and context=\"<selinux context>\"");
+          return FALSE;
+        }
+
+      if (!bus_selinux_id_table_insert (parser->service_sid_table,
+                                        own, context))
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+      
+      return TRUE;
+    }
+  else
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "Element <%s> not allowed inside <%s> in configuration file",
+                      element_name, "selinux");
+      return FALSE;
+    }
+}
+
 dbus_bool_t
 bus_config_parser_start_element (BusConfigParser   *parser,
                                  const char        *element_name,
@@ -1149,6 +1537,12 @@ bus_config_parser_start_element (BusConfigParser   *parser,
                                  attribute_names, attribute_values,
                                  error);
     }
+  else if (t == ELEMENT_SELINUX)
+    {
+      return start_selinux_child (parser, element_name,
+                                  attribute_names, attribute_values,
+                                  error);
+    }
   else
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -1197,6 +1591,12 @@ set_limit (BusConfigParser *parser,
       must_be_int = TRUE;
       parser->limits.auth_timeout = value;
     }
+  else if (strcmp (name, "reply_timeout") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.reply_timeout = value;
+    }
   else if (strcmp (name, "max_completed_connections") == 0)
     {
       must_be_positive = TRUE;
@@ -1215,6 +1615,24 @@ set_limit (BusConfigParser *parser,
       must_be_int = TRUE;
       parser->limits.max_connections_per_user = value;
     }
+  else if (strcmp (name, "max_pending_activations") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.max_pending_activations = value;
+    }
+  else if (strcmp (name, "max_services_per_connection") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.max_services_per_connection = value;
+    }
+  else if (strcmp (name, "max_replies_per_connection") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.max_replies_per_connection = value;
+    }
   else
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -1320,6 +1738,8 @@ bus_config_parser_end_element (BusConfigParser   *parser,
     case ELEMENT_ALLOW:
     case ELEMENT_DENY:
     case ELEMENT_FORK:
+    case ELEMENT_SELINUX:
+    case ELEMENT_ASSOCIATE:
       break;
     }
 
@@ -1370,10 +1790,34 @@ include_file (BusConfigParser   *parser,
    * that the result is the same
    */
   BusConfigParser *included;
+  const char *filename_str;
   DBusError tmp_error;
         
   dbus_error_init (&tmp_error);
-  included = bus_config_load (filename, &tmp_error);
+
+  filename_str = _dbus_string_get_const_data (filename);
+
+  /* Check to make sure this file hasn't already been included. */
+  if (seen_include (parser, filename))
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                     "Circular inclusion of file '%s'",
+                     filename_str);
+      return FALSE;
+    }
+  
+  if (! _dbus_list_append (&parser->included_files, (void *) filename_str))
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  /* Since parser is passed in as the parent, included
+     inherits parser's limits. */
+  included = bus_config_load (filename, FALSE, parser, &tmp_error);
+
+  _dbus_list_pop_last (&parser->included_files);
+
   if (included == NULL)
     {
       _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
@@ -1400,6 +1844,9 @@ include_file (BusConfigParser   *parser,
           return FALSE;
         }
 
+      /* Copy included's limits back to parser. */
+      parser->limits = included->limits;
+
       bus_config_parser_unref (included);
       return TRUE;
     }
@@ -1525,6 +1972,8 @@ bus_config_parser_content (BusConfigParser   *parser,
     case ELEMENT_ALLOW:
     case ELEMENT_DENY:
     case ELEMENT_FORK:
+    case ELEMENT_SELINUX:
+    case ELEMENT_ASSOCIATE:
       if (all_whitespace (content))
         return TRUE;
       else
@@ -1744,7 +2193,7 @@ bus_config_parser_finished (BusConfigParser   *parser,
       return FALSE;
     }
 
-  if (parser->listen_on == NULL)
+  if (parser->is_toplevel && parser->listen_on == NULL)
     {
       dbus_set_error (error, DBUS_ERROR_FAILED,
                       "Configuration file needs one or more <listen> elements giving addresses"); 
@@ -1818,6 +2267,20 @@ bus_config_parser_get_limits (BusConfigParser *parser,
   *limits = parser->limits;
 }
 
+DBusHashTable*
+bus_config_parser_steal_service_sid_table (BusConfigParser *parser)
+{
+  DBusHashTable *table;
+
+  _dbus_assert (parser->service_sid_table != NULL); /* can only steal once */
+
+  table = parser->service_sid_table;
+
+  parser->service_sid_table = NULL;
+
+  return table;
+}
+
 #ifdef DBUS_BUILD_TESTS
 #include <stdio.h>
 
@@ -1838,7 +2301,7 @@ do_load (const DBusString *full_path,
 
   dbus_error_init (&error);
 
-  parser = bus_config_load (full_path, &error);
+  parser = bus_config_load (full_path, TRUE, NULL, &error);
   if (parser == NULL)
     {
       _DBUS_ASSERT_ERROR_IS_SET (&error);
@@ -1895,9 +2358,9 @@ check_loader_oom_func (void *data)
 }
 
 static dbus_bool_t
-process_test_subdir (const DBusString *test_base_dir,
-                     const char       *subdir,
-                     Validity          validity)
+process_test_valid_subdir (const DBusString *test_base_dir,
+                           const char       *subdir,
+                           Validity          validity)
 {
   DBusString test_directory;
   DBusString filename;
@@ -1935,7 +2398,12 @@ process_test_subdir (const DBusString *test_base_dir,
       goto failed;
     }
 
-  printf ("Testing:\n");
+  if (validity == VALID)
+    printf ("Testing valid files:\n");
+  else if (validity == INVALID)
+    printf ("Testing invalid files:\n");
+  else
+    printf ("Testing unknown files:\n");
 
  next:
   while (_dbus_directory_get_next_file (dir, &filename, &error))
@@ -1956,7 +2424,7 @@ process_test_subdir (const DBusString *test_base_dir,
         {
           _dbus_verbose ("Skipping non-.conf file %s\n",
                          _dbus_string_get_const_data (&filename));
-         _dbus_string_free (&full_path);
+          _dbus_string_free (&full_path);
           goto next;
         }
 
@@ -1969,9 +2437,15 @@ process_test_subdir (const DBusString *test_base_dir,
 
       d.full_path = &full_path;
       d.validity = validity;
-      if (!_dbus_test_oom_handling ("config-loader", check_loader_oom_func, &d))
-        _dbus_assert_not_reached ("test failed");
 
+      /* FIXME hackaround for an expat problem, see
+       * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=124747
+       * http://freedesktop.org/pipermail/dbus/2004-May/001153.html
+       */
+      /* if (!_dbus_test_oom_handling ("config-loader", check_loader_oom_func, &d)) */
+      if (!check_loader_oom_func (&d))
+        _dbus_assert_not_reached ("test failed");
+      
       _dbus_string_free (&full_path);
     }
 
@@ -1996,6 +2470,339 @@ process_test_subdir (const DBusString *test_base_dir,
   return retval;
 }
 
+static dbus_bool_t
+bools_equal (dbus_bool_t a,
+            dbus_bool_t b)
+{
+  return a ? b : !b;
+}
+
+static dbus_bool_t
+strings_equal_or_both_null (const char *a,
+                            const char *b)
+{
+  if (a == NULL || b == NULL)
+    return a == b;
+  else
+    return !strcmp (a, b);
+}
+
+static dbus_bool_t
+elements_equal (const Element *a,
+               const Element *b)
+{
+  if (a->type != b->type)
+    return FALSE;
+
+  if (!bools_equal (a->had_content, b->had_content))
+    return FALSE;
+
+  switch (a->type)
+    {
+
+    case ELEMENT_INCLUDE:
+      if (!bools_equal (a->d.include.ignore_missing,
+                       b->d.include.ignore_missing))
+       return FALSE;
+      break;
+
+    case ELEMENT_POLICY:
+      if (a->d.policy.type != b->d.policy.type)
+       return FALSE;
+      if (a->d.policy.gid_or_uid != b->d.policy.gid_or_uid)
+       return FALSE;
+      break;
+
+    case ELEMENT_LIMIT:
+      if (strcmp (a->d.limit.name, b->d.limit.name))
+       return FALSE;
+      if (a->d.limit.value != b->d.limit.value)
+       return FALSE;
+      break;
+
+    default:
+      /* do nothing */
+      break;
+    }
+
+  return TRUE;
+
+}
+
+static dbus_bool_t
+lists_of_elements_equal (DBusList *a,
+                        DBusList *b)
+{
+  DBusList *ia;
+  DBusList *ib;
+
+  ia = a;
+  ib = b;
+  
+  while (ia != NULL && ib != NULL)
+    {
+      if (elements_equal (ia->data, ib->data))
+       return FALSE;
+      ia = _dbus_list_get_next_link (&a, ia);
+      ib = _dbus_list_get_next_link (&b, ib);
+    }
+
+  return ia == NULL && ib == NULL;
+}
+
+static dbus_bool_t
+lists_of_c_strings_equal (DBusList *a,
+                         DBusList *b)
+{
+  DBusList *ia;
+  DBusList *ib;
+
+  ia = a;
+  ib = b;
+  
+  while (ia != NULL && ib != NULL)
+    {
+      if (strcmp (ia->data, ib->data))
+       return FALSE;
+      ia = _dbus_list_get_next_link (&a, ia);
+      ib = _dbus_list_get_next_link (&b, ib);
+    }
+
+  return ia == NULL && ib == NULL;
+}
+
+static dbus_bool_t
+limits_equal (const BusLimits *a,
+             const BusLimits *b)
+{
+  return
+    (a->max_incoming_bytes == b->max_incoming_bytes
+     || a->max_outgoing_bytes == b->max_outgoing_bytes
+     || a->max_message_size == b->max_message_size
+     || a->activation_timeout == b->activation_timeout
+     || a->auth_timeout == b->auth_timeout
+     || a->max_completed_connections == b->max_completed_connections
+     || a->max_incomplete_connections == b->max_incomplete_connections
+     || a->max_connections_per_user == b->max_connections_per_user
+     || a->max_pending_activations == b->max_pending_activations
+     || a->max_services_per_connection == b->max_services_per_connection
+     || a->max_match_rules_per_connection == b->max_match_rules_per_connection
+     || a->max_replies_per_connection == b->max_replies_per_connection
+     || a->reply_timeout == b->reply_timeout);
+}
+
+static dbus_bool_t
+config_parsers_equal (const BusConfigParser *a,
+                      const BusConfigParser *b)
+{
+  if (!_dbus_string_equal (&a->basedir, &b->basedir))
+    return FALSE;
+
+  if (!lists_of_elements_equal (a->stack, b->stack))
+    return FALSE;
+
+  if (!strings_equal_or_both_null (a->user, b->user))
+    return FALSE;
+
+  if (!lists_of_c_strings_equal (a->listen_on, b->listen_on))
+    return FALSE;
+
+  if (!lists_of_c_strings_equal (a->mechanisms, b->mechanisms))
+    return FALSE;
+
+  if (!lists_of_c_strings_equal (a->service_dirs, b->service_dirs))
+    return FALSE;
+  
+  /* FIXME: compare policy */
+
+  /* FIXME: compare service selinux ID table */
+
+  if (! limits_equal (&a->limits, &b->limits))
+    return FALSE;
+
+  if (!strings_equal_or_both_null (a->pidfile, b->pidfile))
+    return FALSE;
+
+  if (! bools_equal (a->fork, b->fork))
+    return FALSE;
+
+  if (! bools_equal (a->is_toplevel, b->is_toplevel))
+    return FALSE;
+
+  return TRUE;
+}
+
+static dbus_bool_t
+all_are_equiv (const DBusString *target_directory)
+{
+  DBusString filename;
+  DBusDirIter *dir;
+  BusConfigParser *first_parser;
+  BusConfigParser *parser;
+  DBusError error;
+  dbus_bool_t equal;
+  dbus_bool_t retval;
+
+  dir = NULL;
+  first_parser = NULL;
+  parser = NULL;
+  retval = FALSE;
+
+  if (!_dbus_string_init (&filename))
+    _dbus_assert_not_reached ("didn't allocate filename string");
+
+  dbus_error_init (&error);
+  dir = _dbus_directory_open (target_directory, &error);
+  if (dir == NULL)
+    {
+      _dbus_warn ("Could not open %s: %s\n",
+                 _dbus_string_get_const_data (target_directory),
+                 error.message);
+      dbus_error_free (&error);
+      goto finished;
+    }
+
+  printf ("Comparing equivalent files:\n");
+
+ next:
+  while (_dbus_directory_get_next_file (dir, &filename, &error))
+    {
+      DBusString full_path;
+
+      if (!_dbus_string_init (&full_path))
+       _dbus_assert_not_reached ("couldn't init string");
+
+      if (!_dbus_string_copy (target_directory, 0, &full_path, 0))
+        _dbus_assert_not_reached ("couldn't copy dir to full_path");
+
+      if (!_dbus_concat_dir_and_file (&full_path, &filename))
+        _dbus_assert_not_reached ("couldn't concat file to dir");
+
+      if (!_dbus_string_ends_with_c_str (&full_path, ".conf"))
+        {
+          _dbus_verbose ("Skipping non-.conf file %s\n",
+                         _dbus_string_get_const_data (&filename));
+         _dbus_string_free (&full_path);
+          goto next;
+        }
+
+      printf ("    %s\n", _dbus_string_get_const_data (&filename));
+
+      parser = bus_config_load (&full_path, TRUE, NULL, &error);
+      _dbus_string_free (&full_path);
+
+      if (parser == NULL)
+       {
+         _dbus_warn ("Could not load file %s: %s\n",
+                     _dbus_string_get_const_data (&full_path),
+                     error.message);
+         dbus_error_free (&error);
+         goto finished;
+       }
+      else if (first_parser == NULL)
+       {
+         first_parser = parser;
+       }
+      else
+       {
+         equal = config_parsers_equal (first_parser, parser);
+         bus_config_parser_unref (parser);
+         if (! equal)
+           goto finished;
+       }
+
+    }
+
+  retval = TRUE;
+
+ finished:
+  _dbus_string_free (&filename);
+  if (first_parser)
+    bus_config_parser_unref (first_parser);
+  if (dir)
+    _dbus_directory_close (dir);
+
+  return retval;
+  
+}
+
+static dbus_bool_t
+process_test_equiv_subdir (const DBusString *test_base_dir,
+                          const char       *subdir)
+{
+  DBusString test_directory;
+  DBusString filename;
+  DBusDirIter *dir;
+  DBusError error;
+  dbus_bool_t equal;
+  dbus_bool_t retval;
+
+  dir = NULL;
+  retval = FALSE;
+
+  if (!_dbus_string_init (&test_directory))
+    _dbus_assert_not_reached ("didn't allocate test_directory");
+
+  _dbus_string_init_const (&filename, subdir);
+
+  if (!_dbus_string_copy (test_base_dir, 0,
+                         &test_directory, 0))
+    _dbus_assert_not_reached ("couldn't copy test_base_dir to test_directory");
+
+  if (!_dbus_concat_dir_and_file (&test_directory, &filename))
+    _dbus_assert_not_reached ("couldn't allocate full path");
+
+  _dbus_string_free (&filename);
+  if (!_dbus_string_init (&filename))
+    _dbus_assert_not_reached ("didn't allocate filename string");
+
+  dbus_error_init (&error);
+  dir = _dbus_directory_open (&test_directory, &error);
+  if (dir == NULL)
+    {
+      _dbus_warn ("Could not open %s: %s\n",
+                 _dbus_string_get_const_data (&test_directory),
+                 error.message);
+      dbus_error_free (&error);
+      goto finished;
+    }
+
+  while (_dbus_directory_get_next_file (dir, &filename, &error))
+    {
+      DBusString full_path;
+
+      /* Skip CVS's magic directories! */
+      if (_dbus_string_equal_c_str (&filename, "CVS"))
+       continue;
+
+      if (!_dbus_string_init (&full_path))
+       _dbus_assert_not_reached ("couldn't init string");
+
+      if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
+        _dbus_assert_not_reached ("couldn't copy dir to full_path");
+
+      if (!_dbus_concat_dir_and_file (&full_path, &filename))
+        _dbus_assert_not_reached ("couldn't concat file to dir");
+      
+      equal = all_are_equiv (&full_path);
+      _dbus_string_free (&full_path);
+
+      if (!equal)
+       goto finished;
+    }
+
+  retval = TRUE;
+
+ finished:
+  _dbus_string_free (&test_directory);
+  _dbus_string_free (&filename);
+  if (dir)
+    _dbus_directory_close (dir);
+
+  return retval;
+  
+}
+                          
 dbus_bool_t
 bus_config_parser_test (const DBusString *test_data_dir)
 {
@@ -2006,7 +2813,13 @@ bus_config_parser_test (const DBusString *test_data_dir)
       return TRUE;
     }
 
-  if (!process_test_subdir (test_data_dir, "valid-config-files", VALID))
+  if (!process_test_valid_subdir (test_data_dir, "valid-config-files", VALID))
+    return FALSE;
+
+  if (!process_test_valid_subdir (test_data_dir, "invalid-config-files", INVALID))
+    return FALSE;
+
+  if (!process_test_equiv_subdir (test_data_dir, "equiv-config-files"))
     return FALSE;
 
   return TRUE;