2003-04-27 Havoc Pennington <hp@pobox.com>
[platform/upstream/dbus.git] / bus / config-parser.c
index 972c05a..90f9efd 100644 (file)
@@ -22,6 +22,8 @@
  */
 #include "config-parser.h"
 #include "test.h"
+#include "utils.h"
+#include "policy.h"
 #include <dbus/dbus-list.h>
 #include <dbus/dbus-internals.h>
 #include <string.h>
@@ -37,9 +39,26 @@ typedef enum
   ELEMENT_POLICY,
   ELEMENT_LIMIT,
   ELEMENT_ALLOW,
-  ELEMENT_DENY
+  ELEMENT_DENY,
+  ELEMENT_FORK,
+  ELEMENT_PIDFILE,
+  ELEMENT_SERVICEDIR,
+  ELEMENT_INCLUDEDIR,
+  ELEMENT_TYPE
 } ElementType;
 
+typedef enum
+{
+  /* we ignore policies for unknown groups/users */
+  POLICY_IGNORED,
+
+  /* non-ignored */
+  POLICY_DEFAULT,
+  POLICY_MANDATORY,
+  POLICY_USER,
+  POLICY_GROUP
+} PolicyType;
+
 typedef struct
 {
   ElementType type;
@@ -55,22 +74,16 @@ typedef struct
 
     struct
     {
-      char *mechanism;
-    } auth;
-
-    struct
-    {
-      char *context;
-      char *user;
-      char *group;
-      DBusList *rules;
+      PolicyType type;
+      unsigned long gid_or_uid;      
     } policy;
 
     struct
     {
-      int foo;
+      char *name;
+      long value;
     } limit;
-
+    
   } d;
 
 } Element;
@@ -79,11 +92,27 @@ struct BusConfigParser
 {
   int refcount;
 
+  DBusString basedir;  /**< Directory we resolve paths relative to */
+  
   DBusList *stack;     /**< stack of Element */
 
   char *user;          /**< user to run as */
 
+  char *bus_type;          /**< Message bus type */
+  
   DBusList *listen_on; /**< List of addresses to listen to */
+
+  DBusList *mechanisms; /**< Auth mechanisms */
+
+  DBusList *service_dirs; /**< Directories to look for services in */
+
+  BusPolicy *policy;     /**< Security policy */
+
+  BusLimits limits;      /**< Limits */
+  
+  unsigned int fork : 1; /**< TRUE to fork into daemon mode */
+
+  char *pidfile;
 };
 
 static const char*
@@ -111,6 +140,16 @@ element_type_to_name (ElementType type)
       return "allow";
     case ELEMENT_DENY:
       return "deny";
+    case ELEMENT_FORK:
+      return "fork";
+    case ELEMENT_PIDFILE:
+      return "pidfile";
+    case ELEMENT_SERVICEDIR:
+      return "servicedir";
+    case ELEMENT_INCLUDEDIR:
+      return "includedir";
+    case ELEMENT_TYPE:
+      return "type";
     }
 
   _dbus_assert_not_reached ("bad element type");
@@ -144,7 +183,9 @@ push_element (BusConfigParser *parser,
 static void
 element_free (Element *e)
 {
-
+  if (e->type == ELEMENT_LIMIT)
+    dbus_free (e->d.limit.name);
+  
   dbus_free (e);
 }
 
@@ -195,14 +236,37 @@ merge_included (BusConfigParser *parser,
       included->user = NULL;
     }
 
+  if (included->bus_type != NULL)
+    {
+      dbus_free (parser->bus_type);
+      parser->bus_type = included->bus_type;
+      included->bus_type = NULL;
+    }
+  
+  if (included->fork)
+    parser->fork = TRUE;
+
+  if (included->pidfile != NULL)
+    {
+      dbus_free (parser->pidfile);
+      parser->pidfile = included->pidfile;
+      included->pidfile = NULL;
+    }
+  
   while ((link = _dbus_list_pop_first_link (&included->listen_on)))
     _dbus_list_append_link (&parser->listen_on, link);
 
+  while ((link = _dbus_list_pop_first_link (&included->mechanisms)))
+    _dbus_list_append_link (&parser->mechanisms, link);
+
+  while ((link = _dbus_list_pop_first_link (&included->service_dirs)))
+    _dbus_list_append_link (&parser->service_dirs, link);
+  
   return TRUE;
 }
 
 BusConfigParser*
-bus_config_parser_new (void)
+bus_config_parser_new (const DBusString *basedir)
 {
   BusConfigParser *parser;
 
@@ -210,6 +274,52 @@ bus_config_parser_new (void)
   if (parser == NULL)
     return NULL;
 
+  if (!_dbus_string_init (&parser->basedir))
+    {
+      dbus_free (parser);
+      return NULL;
+    }
+
+  if (((parser->policy = bus_policy_new ()) == NULL) ||
+      !_dbus_string_copy (basedir, 0, &parser->basedir, 0))
+    {
+      if (parser->policy)
+        bus_policy_unref (parser->policy);
+      
+      _dbus_string_free (&parser->basedir);
+      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;
+
+  /* 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 */
+
+  /* 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->refcount = 1;
 
   return parser;
@@ -236,13 +346,32 @@ bus_config_parser_unref (BusConfigParser *parser)
         pop_element (parser);
 
       dbus_free (parser->user);
-
+      dbus_free (parser->bus_type);
+      dbus_free (parser->pidfile);
+      
       _dbus_list_foreach (&parser->listen_on,
                           (DBusForeachFunction) dbus_free,
                           NULL);
 
       _dbus_list_clear (&parser->listen_on);
 
+      _dbus_list_foreach (&parser->service_dirs,
+                          (DBusForeachFunction) dbus_free,
+                          NULL);
+
+      _dbus_list_clear (&parser->service_dirs);
+
+      _dbus_list_foreach (&parser->mechanisms,
+                          (DBusForeachFunction) dbus_free,
+                          NULL);
+
+      _dbus_list_clear (&parser->mechanisms);
+      
+      _dbus_string_free (&parser->basedir);
+
+      if (parser->policy)
+        bus_policy_unref (parser->policy);
+      
       dbus_free (parser);
     }
 }
@@ -403,7 +532,48 @@ start_busconfig_child (BusConfigParser   *parser,
 
       if (push_element (parser, ELEMENT_USER) == NULL)
         {
-          dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+  else if (strcmp (element_name, "type") == 0)
+    {
+      if (!check_no_attributes (parser, "type", attribute_names, attribute_values, error))
+        return FALSE;
+
+      if (push_element (parser, ELEMENT_TYPE) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+  else if (strcmp (element_name, "fork") == 0)
+    {
+      if (!check_no_attributes (parser, "fork", attribute_names, attribute_values, error))
+        return FALSE;
+
+      if (push_element (parser, ELEMENT_FORK) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      parser->fork = TRUE;
+      
+      return TRUE;
+    }
+  else if (strcmp (element_name, "pidfile") == 0)
+    {
+      if (!check_no_attributes (parser, "pidfile", attribute_names, attribute_values, error))
+        return FALSE;
+
+      if (push_element (parser, ELEMENT_PIDFILE) == NULL)
+        {
+          BUS_SET_OOM (error);
           return FALSE;
         }
 
@@ -416,7 +586,46 @@ start_busconfig_child (BusConfigParser   *parser,
 
       if (push_element (parser, ELEMENT_LISTEN) == NULL)
         {
-          dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+  else if (strcmp (element_name, "auth") == 0)
+    {
+      if (!check_no_attributes (parser, "auth", attribute_names, attribute_values, error))
+        return FALSE;
+
+      if (push_element (parser, ELEMENT_AUTH) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+  else if (strcmp (element_name, "includedir") == 0)
+    {
+      if (!check_no_attributes (parser, "includedir", attribute_names, attribute_values, error))
+        return FALSE;
+
+      if (push_element (parser, ELEMENT_INCLUDEDIR) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+
+      return TRUE;
+    }
+  else if (strcmp (element_name, "servicedir") == 0)
+    {
+      if (!check_no_attributes (parser, "servicedir", attribute_names, attribute_values, error))
+        return FALSE;
+
+      if (push_element (parser, ELEMENT_SERVICEDIR) == NULL)
+        {
+          BUS_SET_OOM (error);
           return FALSE;
         }
 
@@ -429,7 +638,7 @@ start_busconfig_child (BusConfigParser   *parser,
 
       if ((e = push_element (parser, ELEMENT_INCLUDE)) == NULL)
         {
-          dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          BUS_SET_OOM (error);
           return FALSE;
         }
 
@@ -468,11 +677,13 @@ start_busconfig_child (BusConfigParser   *parser,
 
       if ((e = push_element (parser, ELEMENT_POLICY)) == NULL)
         {
-          dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          BUS_SET_OOM (error);
           return FALSE;
         }
 
-      if (!locate_attributes (parser, "include",
+      e->d.policy.type = POLICY_IGNORED;
+      
+      if (!locate_attributes (parser, "policy",
                               attribute_names,
                               attribute_values,
                               error,
@@ -482,103 +693,463 @@ start_busconfig_child (BusConfigParser   *parser,
                               NULL))
         return FALSE;
 
-      /* FIXME */
+      if (((context && user) ||
+           (context && group)) ||
+          (user && group) ||
+          !(context || user || group))
+        {
+          dbus_set_error (error, DBUS_ERROR_FAILED,
+                          "<policy> element must have exactly one of (context|user|group) attributes");
+          return FALSE;
+        }
+
+      if (context != NULL)
+        {
+          if (strcmp (context, "default") == 0)
+            {
+              e->d.policy.type = POLICY_DEFAULT;
+            }
+          else if (strcmp (context, "mandatory") == 0)
+            {
+              e->d.policy.type = POLICY_MANDATORY;
+            }
+          else
+            {
+              dbus_set_error (error, DBUS_ERROR_FAILED,
+                              "context attribute on <policy> must have the value \"default\" or \"mandatory\", not \"%s\"",
+                              context);
+              return FALSE;
+            }
+        }
+      else if (user != NULL)
+        {
+          DBusString username;
+          _dbus_string_init_const (&username, user);
+
+          if (_dbus_get_user_id (&username,
+                                 &e->d.policy.gid_or_uid))
+            e->d.policy.type = POLICY_USER;
+          else
+            _dbus_warn ("Unknown username \"%s\" in message bus configuration file\n",
+                        user);
+        }
+      else if (group != NULL)
+        {
+          DBusString group_name;
+          _dbus_string_init_const (&group_name, group);
+
+          if (_dbus_get_group_id (&group_name,
+                                  &e->d.policy.gid_or_uid))
+            e->d.policy.type = POLICY_GROUP;
+          else
+            _dbus_warn ("Unknown group \"%s\" in message bus configuration file\n",
+                        group);          
+        }
+      else
+        {
+          _dbus_assert_not_reached ("all <policy> attributes null and we didn't set error");
+        }
       
       return TRUE;
     }
-  else
+  else if (strcmp (element_name, "limit") == 0)
     {
-      dbus_set_error (error, DBUS_ERROR_FAILED,
-                      "Element <%s> not allowed inside <%s> in configuration file",
-                      element_name, "busconfig");
-      return FALSE;
-    }
-}
+      Element *e;
+      const char *name;
 
-static dbus_bool_t
-start_policy_child (BusConfigParser   *parser,
-                    const char        *element_name,
-                    const char       **attribute_names,
-                    const char       **attribute_values,
-                    DBusError         *error)
-{
-  if (strcmp (element_name, "allow") == 0)
-    {
-      if (push_element (parser, ELEMENT_ALLOW) == NULL)
+      if ((e = push_element (parser, ELEMENT_LIMIT)) == NULL)
         {
-          dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          BUS_SET_OOM (error);
           return FALSE;
         }
       
-      return TRUE;
-    }
-  else if (strcmp (element_name, "deny") == 0)
-    {
-      if (push_element (parser, ELEMENT_DENY) == NULL)
+      if (!locate_attributes (parser, "limit",
+                              attribute_names,
+                              attribute_values,
+                              error,
+                              "name", &name,
+                              NULL))
+        return FALSE;
+
+      if (name == NULL)
         {
-          dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          dbus_set_error (error, DBUS_ERROR_FAILED,
+                          "<limit> element must have a \"name\" attribute");
           return FALSE;
         }
-      
+
+      e->d.limit.name = _dbus_strdup (name);
+      if (e->d.limit.name == NULL)
+        {
+          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, "policy");
+                      element_name, "busconfig");
       return FALSE;
     }
 }
 
-dbus_bool_t
-bus_config_parser_start_element (BusConfigParser   *parser,
-                                 const char        *element_name,
-                                 const char       **attribute_names,
-                                 const char       **attribute_values,
-                                 DBusError         *error)
+static dbus_bool_t
+append_rule_from_element (BusConfigParser   *parser,
+                          const char        *element_name,
+                          const char       **attribute_names,
+                          const char       **attribute_values,
+                          dbus_bool_t        allow,
+                          DBusError         *error)
 {
-  ElementType t;
-
-  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+  const char *send;
+  const char *receive;
+  const char *own;
+  const char *send_to;
+  const char *receive_from;
+  const char *user;
+  const char *group;
+  BusPolicyRule *rule;
+  
+  if (!locate_attributes (parser, element_name,
+                          attribute_names,
+                          attribute_values,
+                          error,
+                          "send", &send,
+                          "receive", &receive,
+                          "own", &own,
+                          "send_to", &send_to,
+                          "receive_from", &receive_from,
+                          "user", &user,
+                          "group", &group,
+                          NULL))
+    return FALSE;
 
-  /* printf ("START: %s\n", element_name); */
+  if (!(send || receive || own || send_to || receive_from ||
+        user || group))
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "Element <%s> must have one or more attributes",
+                      element_name);
+      return FALSE;
+    }
   
-  t = top_element_type (parser);
+  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)) ||
+
+      ((send_to && receive_from) ||
+       (send_to && user) ||
+       (send_to && group)) ||
+
+      ((receive_from && user) ||
+       (receive_from && 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",
+                      element_name);
+      return FALSE;
+    }
 
-  if (t == ELEMENT_NONE)
+  rule = NULL;
+
+  /* In BusPolicyRule, NULL represents wildcard.
+   * In the config file, '*' represents it.
+   */
+#define IS_WILDCARD(str) ((str) && ((str)[0]) == '*' && ((str)[1]) == '\0')
+
+  if (send || send_to)
     {
-      if (strcmp (element_name, "busconfig") == 0)
+      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)
+        goto nomem;
+      if (send_to && rule->d.send.destination == NULL)
+        goto nomem;
+    }
+  else if (receive || receive_from)
+    {
+      rule = bus_policy_rule_new (BUS_POLICY_RULE_RECEIVE, allow); 
+      if (rule == NULL)
+        goto nomem;
+
+      if (IS_WILDCARD (receive))
+        receive = NULL;
+
+      if (IS_WILDCARD (receive_from))
+        receive_from = NULL;
+      
+      rule->d.receive.message_name = _dbus_strdup (receive);
+      rule->d.receive.origin = _dbus_strdup (receive_from);
+      if (receive && rule->d.receive.message_name == NULL)
+        goto nomem;
+      if (receive_from && rule->d.receive.origin == NULL)
+        goto nomem;
+    }
+  else if (own)
+    {
+      rule = bus_policy_rule_new (BUS_POLICY_RULE_OWN, allow); 
+      if (rule == NULL)
+        goto nomem;
+
+      if (IS_WILDCARD (own))
+        own = NULL;
+      
+      rule->d.own.service_name = _dbus_strdup (own);
+      if (own && rule->d.own.service_name == NULL)
+        goto nomem;
+    }
+  else if (user)
+    {      
+      if (IS_WILDCARD (user))
         {
-          if (!check_no_attributes (parser, "busconfig", attribute_names, attribute_values, error))
-            return FALSE;
+          rule = bus_policy_rule_new (BUS_POLICY_RULE_USER, allow); 
+          if (rule == NULL)
+            goto nomem;
+
+          rule->d.user.uid = DBUS_UID_UNSET;
+        }
+      else
+        {
+          DBusString username;
+          dbus_uid_t uid;
           
-          if (push_element (parser, ELEMENT_BUSCONFIG) == NULL)
+          _dbus_string_init_const (&username, user);
+      
+          if (_dbus_get_user_id (&username, &uid))
             {
-              dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
-              return FALSE;
+              rule = bus_policy_rule_new (BUS_POLICY_RULE_USER, allow); 
+              if (rule == NULL)
+                goto nomem;
+
+              rule->d.user.uid = uid;
             }
+          else
+            {
+              _dbus_warn ("Unknown username \"%s\" on element <%s>\n",
+                          user, element_name);
+            }
+        }
+    }
+  else if (group)
+    {
+      if (IS_WILDCARD (group))
+        {
+          rule = bus_policy_rule_new (BUS_POLICY_RULE_GROUP, allow); 
+          if (rule == NULL)
+            goto nomem;
 
-          return TRUE;
+          rule->d.group.gid = DBUS_GID_UNSET;
         }
       else
         {
-          dbus_set_error (error, DBUS_ERROR_FAILED,
-                          "Unknown element <%s> at root of configuration file",
-                          element_name);
-          return FALSE;
+          DBusString groupname;
+          dbus_gid_t gid;
+          
+          _dbus_string_init_const (&groupname, group);
+          
+          if (_dbus_get_user_id (&groupname, &gid))
+            {
+              rule = bus_policy_rule_new (BUS_POLICY_RULE_GROUP, allow); 
+              if (rule == NULL)
+                goto nomem;
+
+              rule->d.group.gid = gid;
+            }
+          else
+            {
+              _dbus_warn ("Unknown group \"%s\" on element <%s>\n",
+                          group, element_name);
+            }
         }
     }
-  else if (t == ELEMENT_BUSCONFIG)
-    {
-      return start_busconfig_child (parser, element_name,
-                                    attribute_names, attribute_values,
-                                    error);
-    }
-  else if (t == ELEMENT_POLICY)
+  else
+    _dbus_assert_not_reached ("Did not handle some combination of attributes on <allow> or <deny>");
+
+  if (rule != NULL)
     {
-      return start_policy_child (parser, element_name,
-                                 attribute_names, attribute_values,
+      Element *pe;
+      
+      pe = peek_element (parser);      
+      _dbus_assert (pe != NULL);
+      _dbus_assert (pe->type == ELEMENT_POLICY);
+
+      switch (pe->d.policy.type)
+        {
+        case POLICY_IGNORED:
+          /* drop the rule on the floor */
+          break;
+          
+        case POLICY_DEFAULT:
+          if (!bus_policy_append_default_rule (parser->policy, rule))
+            goto nomem;
+          break;
+        case POLICY_MANDATORY:
+          if (!bus_policy_append_mandatory_rule (parser->policy, rule))
+            goto nomem;
+          break;
+        case POLICY_USER:
+          if (!BUS_POLICY_RULE_IS_PER_CLIENT (rule))
+            {
+              dbus_set_error (error, DBUS_ERROR_FAILED,
+                              "<%s> rule cannot be per-user because it has bus-global semantics",
+                              element_name);
+              goto failed;
+            }
+          
+          if (!bus_policy_append_user_rule (parser->policy, pe->d.policy.gid_or_uid,
+                                            rule))
+            goto nomem;
+          break;
+        case POLICY_GROUP:
+          if (!BUS_POLICY_RULE_IS_PER_CLIENT (rule))
+            {
+              dbus_set_error (error, DBUS_ERROR_FAILED,
+                              "<%s> rule cannot be per-group because it has bus-global semantics",
+                              element_name);
+              goto failed;
+            }
+          
+          if (!bus_policy_append_group_rule (parser->policy, pe->d.policy.gid_or_uid,
+                                             rule))
+            goto nomem;
+          break;
+        }
+      
+      bus_policy_rule_unref (rule);
+      rule = NULL;
+    }
+  
+  return TRUE;
+
+ nomem:
+  BUS_SET_OOM (error);
+ failed:
+  if (rule)
+    bus_policy_rule_unref (rule);
+  return FALSE;
+}
+
+static dbus_bool_t
+start_policy_child (BusConfigParser   *parser,
+                    const char        *element_name,
+                    const char       **attribute_names,
+                    const char       **attribute_values,
+                    DBusError         *error)
+{
+  if (strcmp (element_name, "allow") == 0)
+    {
+      if (!append_rule_from_element (parser, element_name,
+                                     attribute_names, attribute_values,
+                                     TRUE, error))
+        return FALSE;
+      
+      if (push_element (parser, ELEMENT_ALLOW) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+      
+      return TRUE;
+    }
+  else if (strcmp (element_name, "deny") == 0)
+    {
+      if (!append_rule_from_element (parser, element_name,
+                                     attribute_names, attribute_values,
+                                     FALSE, error))
+        return FALSE;
+      
+      if (push_element (parser, ELEMENT_DENY) == NULL)
+        {
+          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, "policy");
+      return FALSE;
+    }
+}
+
+dbus_bool_t
+bus_config_parser_start_element (BusConfigParser   *parser,
+                                 const char        *element_name,
+                                 const char       **attribute_names,
+                                 const char       **attribute_values,
+                                 DBusError         *error)
+{
+  ElementType t;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+  /* printf ("START: %s\n", element_name); */
+  
+  t = top_element_type (parser);
+
+  if (t == ELEMENT_NONE)
+    {
+      if (strcmp (element_name, "busconfig") == 0)
+        {
+          if (!check_no_attributes (parser, "busconfig", attribute_names, attribute_values, error))
+            return FALSE;
+          
+          if (push_element (parser, ELEMENT_BUSCONFIG) == NULL)
+            {
+              BUS_SET_OOM (error);
+              return FALSE;
+            }
+
+          return TRUE;
+        }
+      else
+        {
+          dbus_set_error (error, DBUS_ERROR_FAILED,
+                          "Unknown element <%s> at root of configuration file",
+                          element_name);
+          return FALSE;
+        }
+    }
+  else if (t == ELEMENT_BUSCONFIG)
+    {
+      return start_busconfig_child (parser, element_name,
+                                    attribute_names, attribute_values,
+                                    error);
+    }
+  else if (t == ELEMENT_POLICY)
+    {
+      return start_policy_child (parser, element_name,
+                                 attribute_names, attribute_values,
                                  error);
     }
   else
@@ -590,6 +1161,103 @@ bus_config_parser_start_element (BusConfigParser   *parser,
     }  
 }
 
+static dbus_bool_t
+set_limit (BusConfigParser *parser,
+           const char      *name,
+           long             value,
+           DBusError       *error)
+{
+  dbus_bool_t must_be_positive;
+  dbus_bool_t must_be_int;
+
+  must_be_int = FALSE;
+  must_be_positive = FALSE;
+  
+  if (strcmp (name, "max_incoming_bytes") == 0)
+    {
+      must_be_positive = TRUE;
+      parser->limits.max_incoming_bytes = value;
+    }
+  else if (strcmp (name, "max_outgoing_bytes") == 0)
+    {
+      must_be_positive = TRUE;
+      parser->limits.max_outgoing_bytes = value;
+    }
+  else if (strcmp (name, "max_message_size") == 0)
+    {
+      must_be_positive = TRUE;
+      parser->limits.max_message_size = value;
+    }
+  else if (strcmp (name, "activation_timeout") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.activation_timeout = value;
+    }
+  else if (strcmp (name, "auth_timeout") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.auth_timeout = value;
+    }
+  else if (strcmp (name, "max_completed_connections") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.max_completed_connections = value;
+    }
+  else if (strcmp (name, "max_incomplete_connections") == 0)
+    {
+      must_be_positive = TRUE;
+      must_be_int = TRUE;
+      parser->limits.max_incomplete_connections = value;
+    }
+  else if (strcmp (name, "max_connections_per_user") == 0)
+    {
+      must_be_positive = TRUE;
+      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
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "There is no limit called \"%s\"\n",
+                      name);
+      return FALSE;
+    }
+  
+  if (must_be_positive && value < 0)
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "<limit name=\"%s\"> must be a positive number\n",
+                      name);
+      return FALSE;
+    }
+
+  if (must_be_int &&
+      (value < _DBUS_INT_MIN || value > _DBUS_INT_MAX))
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "<limit name=\"%s\"> value is too large\n",
+                      name);
+      return FALSE;
+    }
+
+  return TRUE;  
+}
+
 dbus_bool_t
 bus_config_parser_end_element (BusConfigParser   *parser,
                                const char        *element_name,
@@ -623,7 +1291,8 @@ bus_config_parser_end_element (BusConfigParser   *parser,
        * being paranoid about XML parsers
        */
       dbus_set_error (error, DBUS_ERROR_FAILED,
-                      "XML element ended which was not the topmost element on the stack");
+                      "XML element <%s> ended but topmost element on the stack was <%s>",
+                      element_name, n);
       return FALSE;
     }
 
@@ -638,8 +1307,13 @@ bus_config_parser_end_element (BusConfigParser   *parser,
 
     case ELEMENT_INCLUDE:
     case ELEMENT_USER:
+    case ELEMENT_TYPE:
     case ELEMENT_LISTEN:
+    case ELEMENT_PIDFILE:
     case ELEMENT_AUTH:
+    case ELEMENT_SERVICEDIR:
+    case ELEMENT_INCLUDEDIR:
+    case ELEMENT_LIMIT:
       if (!e->had_content)
         {
           dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -647,13 +1321,20 @@ bus_config_parser_end_element (BusConfigParser   *parser,
                           element_type_to_name (e->type));
           return FALSE;
         }
+
+      if (e->type == ELEMENT_LIMIT)
+        {
+          if (!set_limit (parser, e->d.limit.name, e->d.limit.value,
+                          error))
+            return FALSE;
+        }
       break;
 
     case ELEMENT_BUSCONFIG:
     case ELEMENT_POLICY:
-    case ELEMENT_LIMIT:
     case ELEMENT_ALLOW:
     case ELEMENT_DENY:
+    case ELEMENT_FORK:
       break;
     }
 
@@ -672,6 +1353,150 @@ all_whitespace (const DBusString *str)
   return i == _dbus_string_get_length (str);
 }
 
+static dbus_bool_t
+make_full_path (const DBusString *basedir,
+                const DBusString *filename,
+                DBusString       *full_path)
+{
+  if (_dbus_path_is_absolute (filename))
+    {
+      return _dbus_string_copy (filename, 0, full_path, 0);
+    }
+  else
+    {
+      if (!_dbus_string_copy (basedir, 0, full_path, 0))
+        return FALSE;
+      
+      if (!_dbus_concat_dir_and_file (full_path, filename))
+        return FALSE;
+
+      return TRUE;
+    }
+}
+
+static dbus_bool_t
+include_file (BusConfigParser   *parser,
+              const DBusString  *filename,
+              dbus_bool_t        ignore_missing,
+              DBusError         *error)
+{
+  /* FIXME good test case for this would load each config file in the
+   * test suite both alone, and as an include, and check
+   * that the result is the same
+   */
+  BusConfigParser *included;
+  DBusError tmp_error;
+        
+  dbus_error_init (&tmp_error);
+  included = bus_config_load (filename, &tmp_error);
+  if (included == NULL)
+    {
+      _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
+
+      if (dbus_error_has_name (&tmp_error, DBUS_ERROR_FILE_NOT_FOUND) &&
+          ignore_missing)
+        {
+          dbus_error_free (&tmp_error);
+          return TRUE;
+        }
+      else
+        {
+          dbus_move_error (&tmp_error, error);
+          return FALSE;
+        }
+    }
+  else
+    {
+      _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error);
+
+      if (!merge_included (parser, included, error))
+        {
+          bus_config_parser_unref (included);
+          return FALSE;
+        }
+
+      bus_config_parser_unref (included);
+      return TRUE;
+    }
+}
+
+static dbus_bool_t
+include_dir (BusConfigParser   *parser,
+             const DBusString  *dirname,
+             DBusError         *error)
+{
+  DBusString filename;
+  dbus_bool_t retval;
+  DBusError tmp_error;
+  DBusDirIter *dir;
+  
+  if (!_dbus_string_init (&filename))
+    {
+      BUS_SET_OOM (error);
+      return FALSE;
+    }
+
+  retval = FALSE;
+  
+  dir = _dbus_directory_open (dirname, error);
+
+  if (dir == NULL)
+    goto failed;
+
+  dbus_error_init (&tmp_error);
+  while (_dbus_directory_get_next_file (dir, &filename, &tmp_error))
+    {
+      DBusString full_path;
+
+      if (!_dbus_string_init (&full_path))
+        {
+          BUS_SET_OOM (error);
+          goto failed;
+        }
+
+      if (!_dbus_string_copy (dirname, 0, &full_path, 0))
+        {
+          BUS_SET_OOM (error);
+          _dbus_string_free (&full_path);
+          goto failed;
+        }      
+
+      if (!_dbus_concat_dir_and_file (&full_path, &filename))
+        {
+          BUS_SET_OOM (error);
+          _dbus_string_free (&full_path);
+          goto failed;
+        }
+      
+      if (_dbus_string_ends_with_c_str (&full_path, ".conf"))
+        {
+          if (!include_file (parser, &full_path, TRUE, error))
+            {
+              _dbus_string_free (&full_path);
+              goto failed;
+            }
+        }
+
+      _dbus_string_free (&full_path);
+    }
+
+  if (dbus_error_is_set (&tmp_error))
+    {
+      dbus_move_error (&tmp_error, error);
+      goto failed;
+    }
+  
+  retval = TRUE;
+  
+ failed:
+  _dbus_string_free (&filename);
+  
+  if (dir)
+    _dbus_directory_close (dir);
+
+  return retval;
+}
+
 dbus_bool_t
 bus_config_parser_content (BusConfigParser   *parser,
                            const DBusString  *content,
@@ -712,9 +1537,9 @@ bus_config_parser_content (BusConfigParser   *parser,
 
     case ELEMENT_BUSCONFIG:
     case ELEMENT_POLICY:
-    case ELEMENT_LIMIT:
     case ELEMENT_ALLOW:
     case ELEMENT_DENY:
+    case ELEMENT_FORK:
       if (all_whitespace (content))
         return TRUE;
       else
@@ -725,47 +1550,71 @@ bus_config_parser_content (BusConfigParser   *parser,
           return FALSE;
         }
 
-    case ELEMENT_INCLUDE:
+    case ELEMENT_PIDFILE:
       {
-        /* FIXME good test case for this would load each config file in the
-         * test suite both alone, and as an include, and check
-         * that the result is the same
-         */
-        BusConfigParser *included;
-        DBusError tmp_error;
+        char *s;
+
+        e->had_content = TRUE;
+        
+        if (!_dbus_string_copy_data (content, &s))
+          goto nomem;
+          
+        dbus_free (parser->pidfile);
+        parser->pidfile = s;
+      }
+      break;
 
+    case ELEMENT_INCLUDE:
+      {
+        DBusString full_path;
+        
         e->had_content = TRUE;
+
+        if (!_dbus_string_init (&full_path))
+          goto nomem;
         
-        dbus_error_init (&tmp_error);
-        included = bus_config_load (content, &tmp_error);
-        if (included == NULL)
+        if (!make_full_path (&parser->basedir, content, &full_path))
           {
-            _DBUS_ASSERT_ERROR_IS_SET (&tmp_error);
-            if (dbus_error_has_name (&tmp_error, DBUS_ERROR_FILE_NOT_FOUND) &&
-                e->d.include.ignore_missing)
-              {
-                dbus_error_free (&tmp_error);
-                return TRUE;
-              }
-            else
-              {
-                dbus_move_error (&tmp_error, error);
-                return FALSE;
-              }
+            _dbus_string_free (&full_path);
+            goto nomem;
           }
-        else
+        
+        if (!include_file (parser, &full_path,
+                           e->d.include.ignore_missing, error))
           {
-            _DBUS_ASSERT_ERROR_IS_CLEAR (&tmp_error);
+            _dbus_string_free (&full_path);
+            return FALSE;
+          }
 
-            if (!merge_included (parser, included, error))
-              return FALSE;
+        _dbus_string_free (&full_path);
+      }
+      break;
+
+    case ELEMENT_INCLUDEDIR:
+      {
+        DBusString full_path;
+        
+        e->had_content = TRUE;
 
-            bus_config_parser_unref (included);
-            return TRUE;
+        if (!_dbus_string_init (&full_path))
+          goto nomem;
+        
+        if (!make_full_path (&parser->basedir, content, &full_path))
+          {
+            _dbus_string_free (&full_path);
+            goto nomem;
+          }
+        
+        if (!include_dir (parser, &full_path, error))
+          {
+            _dbus_string_free (&full_path);
+            return FALSE;
           }
+
+        _dbus_string_free (&full_path);
       }
       break;
-
+      
     case ELEMENT_USER:
       {
         char *s;
@@ -780,6 +1629,20 @@ bus_config_parser_content (BusConfigParser   *parser,
       }
       break;
 
+    case ELEMENT_TYPE:
+      {
+        char *s;
+
+        e->had_content = TRUE;
+
+        if (!_dbus_string_copy_data (content, &s))
+          goto nomem;
+        
+        dbus_free (parser->bus_type);
+        parser->bus_type = s;
+      }
+      break;
+      
     case ELEMENT_LISTEN:
       {
         char *s;
@@ -800,8 +1663,75 @@ bus_config_parser_content (BusConfigParser   *parser,
 
     case ELEMENT_AUTH:
       {
+        char *s;
+        
+        e->had_content = TRUE;
+
+        if (!_dbus_string_copy_data (content, &s))
+          goto nomem;
+
+        if (!_dbus_list_append (&parser->mechanisms,
+                                s))
+          {
+            dbus_free (s);
+            goto nomem;
+          }
+      }
+      break;
+
+    case ELEMENT_SERVICEDIR:
+      {
+        char *s;
+        DBusString full_path;
+        
         e->had_content = TRUE;
-        /* FIXME */
+
+        if (!_dbus_string_init (&full_path))
+          goto nomem;
+        
+        if (!make_full_path (&parser->basedir, content, &full_path))
+          {
+            _dbus_string_free (&full_path);
+            goto nomem;
+          }
+        
+        if (!_dbus_string_copy_data (&full_path, &s))
+          {
+            _dbus_string_free (&full_path);
+            goto nomem;
+          }
+
+        if (!_dbus_list_append (&parser->service_dirs, s))
+          {
+            _dbus_string_free (&full_path);
+            dbus_free (s);
+            goto nomem;
+          }
+
+        _dbus_string_free (&full_path);
+      }
+      break;
+
+    case ELEMENT_LIMIT:
+      {
+        long val;
+
+        e->had_content = TRUE;
+
+        val = 0;
+        if (!_dbus_string_parse_int (content, 0, &val, NULL))
+          {
+            dbus_set_error (error, DBUS_ERROR_FAILED,
+                            "<limit name=\"%s\"> element has invalid value (could not parse as integer)",
+                            e->d.limit.name);
+            return FALSE;
+          }
+
+        e->d.limit.value = val;
+
+        _dbus_verbose ("Loaded value %ld for limit %s\n",
+                       e->d.limit.value,
+                       e->d.limit.name);
       }
       break;
     }
@@ -810,7 +1740,7 @@ bus_config_parser_content (BusConfigParser   *parser,
   return TRUE;
 
  nomem:
-  dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+  BUS_SET_OOM (error);
   return FALSE;
 }
 
@@ -828,6 +1758,13 @@ bus_config_parser_finished (BusConfigParser   *parser,
 
       return FALSE;
     }
+
+  if (parser->listen_on == NULL)
+    {
+      dbus_set_error (error, DBUS_ERROR_FAILED,
+                      "Configuration file needs one or more <listen> elements giving addresses"); 
+      return FALSE;
+    }
   
   return TRUE;
 }
@@ -838,6 +1775,64 @@ bus_config_parser_get_user (BusConfigParser *parser)
   return parser->user;
 }
 
+const char*
+bus_config_parser_get_type (BusConfigParser *parser)
+{
+  return parser->bus_type;
+}
+
+DBusList**
+bus_config_parser_get_addresses (BusConfigParser *parser)
+{
+  return &parser->listen_on;
+}
+
+DBusList**
+bus_config_parser_get_mechanisms (BusConfigParser *parser)
+{
+  return &parser->mechanisms;
+}
+
+DBusList**
+bus_config_parser_get_service_dirs (BusConfigParser *parser)
+{
+  return &parser->service_dirs;
+}
+
+dbus_bool_t
+bus_config_parser_get_fork (BusConfigParser   *parser)
+{
+  return parser->fork;
+}
+
+const char *
+bus_config_parser_get_pidfile (BusConfigParser   *parser)
+{
+  return parser->pidfile;
+}
+
+BusPolicy*
+bus_config_parser_steal_policy (BusConfigParser *parser)
+{
+  BusPolicy *policy;
+
+  _dbus_assert (parser->policy != NULL); /* can only steal the policy 1 time */
+  
+  policy = parser->policy;
+
+  parser->policy = NULL;
+
+  return policy;
+}
+
+/* Overwrite any limits that were set in the configuration file */
+void
+bus_config_parser_get_limits (BusConfigParser *parser,
+                              BusLimits       *limits)
+{
+  *limits = parser->limits;
+}
+
 #ifdef DBUS_BUILD_TESTS
 #include <stdio.h>
 
@@ -928,7 +1923,7 @@ process_test_subdir (const DBusString *test_base_dir,
   retval = FALSE;
   dir = NULL;
 
-  if (!_dbus_string_init (&test_directory, _DBUS_INT_MAX))
+  if (!_dbus_string_init (&test_directory))
     _dbus_assert_not_reached ("didn't allocate test_directory\n");
 
   _dbus_string_init_const (&filename, subdir);
@@ -941,16 +1936,15 @@ process_test_subdir (const DBusString *test_base_dir,
     _dbus_assert_not_reached ("couldn't allocate full path");
 
   _dbus_string_free (&filename);
-  if (!_dbus_string_init (&filename, _DBUS_INT_MAX))
+  if (!_dbus_string_init (&filename))
     _dbus_assert_not_reached ("didn't allocate filename string\n");
 
   dbus_error_init (&error);
   dir = _dbus_directory_open (&test_directory, &error);
   if (dir == NULL)
     {
-      const char *s;
-      _dbus_string_get_const_data (&test_directory, &s);
-      _dbus_warn ("Could not open %s: %s\n", s,
+      _dbus_warn ("Could not open %s: %s\n",
+                  _dbus_string_get_const_data (&test_directory),
                   error.message);
       dbus_error_free (&error);
       goto failed;
@@ -964,7 +1958,7 @@ process_test_subdir (const DBusString *test_base_dir,
       DBusString full_path;
       LoaderOomData d;
 
-      if (!_dbus_string_init (&full_path, _DBUS_INT_MAX))
+      if (!_dbus_string_init (&full_path))
         _dbus_assert_not_reached ("couldn't init string");
 
       if (!_dbus_string_copy (&test_directory, 0, &full_path, 0))
@@ -975,19 +1969,13 @@ process_test_subdir (const DBusString *test_base_dir,
 
       if (!_dbus_string_ends_with_c_str (&full_path, ".conf"))
         {
-          const char *filename_c;
-          _dbus_string_get_const_data (&filename, &filename_c);
           _dbus_verbose ("Skipping non-.conf file %s\n",
-                         filename_c);
+                         _dbus_string_get_const_data (&filename));
          _dbus_string_free (&full_path);
           goto next;
         }
 
-      {
-        const char *s;
-        _dbus_string_get_const_data (&filename, &s);
-        printf ("    %s\n", s);
-      }
+      printf ("    %s\n", _dbus_string_get_const_data (&filename));
 
       _dbus_verbose (" expecting %s\n",
                      validity == VALID ? "valid" :
@@ -1004,10 +1992,9 @@ process_test_subdir (const DBusString *test_base_dir,
 
   if (dbus_error_is_set (&error))
     {
-      const char *s;
-      _dbus_string_get_const_data (&test_directory, &s);
       _dbus_warn ("Could not get next file in %s: %s\n",
-                  s, error.message);
+                  _dbus_string_get_const_data (&test_directory),
+                  error.message);
       dbus_error_free (&error);
       goto failed;
     }