2003-04-25 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / bus / config-parser.c
index 780d175..5e27963 100644 (file)
@@ -47,6 +47,18 @@ typedef enum
   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;
@@ -62,17 +74,16 @@ typedef struct
 
     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;
@@ -96,6 +107,8 @@ struct BusConfigParser
   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 */
 
@@ -170,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);
 }
 
@@ -268,10 +283,42 @@ bus_config_parser_new (const DBusString *basedir)
   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;
+  
+#ifdef DBUS_BUILD_TESTS
+  parser->limits.activation_timeout = 6000;  /* 6 seconds */
+#else
+  parser->limits.activation_timeout = 15000; /* 15 seconds */
+#endif
+
+  /* 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;
 
@@ -634,6 +681,8 @@ start_busconfig_child (BusConfigParser   *parser,
           return FALSE;
         }
 
+      e->d.policy.type = POLICY_IGNORED;
+      
       if (!locate_attributes (parser, "policy",
                               attribute_names,
                               attribute_values,
@@ -658,11 +707,11 @@ start_busconfig_child (BusConfigParser   *parser,
         {
           if (strcmp (context, "default") == 0)
             {
-
+              e->d.policy.type = POLICY_DEFAULT;
             }
           else if (strcmp (context, "mandatory") == 0)
             {
-
+              e->d.policy.type = POLICY_MANDATORY;
             }
           else
             {
@@ -671,19 +720,30 @@ start_busconfig_child (BusConfigParser   *parser,
                               context);
               return FALSE;
             }
-          
-          /* FIXME */
-
         }
       else if (user != NULL)
         {
-          /* FIXME */
+          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)
         {
-          /* FIXME */
+          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
         {
@@ -692,6 +752,41 @@ start_busconfig_child (BusConfigParser   *parser,
       
       return TRUE;
     }
+  else if (strcmp (element_name, "limit") == 0)
+    {
+      Element *e;
+      const char *name;
+
+      if ((e = push_element (parser, ELEMENT_LIMIT)) == NULL)
+        {
+          BUS_SET_OOM (error);
+          return FALSE;
+        }
+      
+      if (!locate_attributes (parser, "limit",
+                              attribute_names,
+                              attribute_values,
+                              error,
+                              "name", &name,
+                              NULL))
+        return FALSE;
+
+      if (name == 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,
@@ -702,6 +797,266 @@ start_busconfig_child (BusConfigParser   *parser,
 }
 
 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)
+{
+  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;
+
+  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;
+    }
+  
+  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;
+    }
+
+  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)
+    {
+      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))
+        {
+          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;
+          
+          _dbus_string_init_const (&username, user);
+      
+          if (_dbus_get_user_id (&username, &uid))
+            {
+              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;
+
+          rule->d.group.gid = DBUS_GID_UNSET;
+        }
+      else
+        {
+          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
+    _dbus_assert_not_reached ("Did not handle some combination of attributes on <allow> or <deny>");
+
+  if (rule != NULL)
+    {
+      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,
@@ -710,6 +1065,11 @@ start_policy_child (BusConfigParser   *parser,
 {
   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);
@@ -720,6 +1080,11 @@ start_policy_child (BusConfigParser   *parser,
     }
   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);
@@ -796,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,
@@ -851,6 +1313,7 @@ bus_config_parser_end_element (BusConfigParser   *parser,
     case ELEMENT_AUTH:
     case ELEMENT_SERVICEDIR:
     case ELEMENT_INCLUDEDIR:
+    case ELEMENT_LIMIT:
       if (!e->had_content)
         {
           dbus_set_error (error, DBUS_ERROR_FAILED,
@@ -858,11 +1321,17 @@ 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:
@@ -1068,7 +1537,6 @@ bus_config_parser_content (BusConfigParser   *parser,
 
     case ELEMENT_BUSCONFIG:
     case ELEMENT_POLICY:
-    case ELEMENT_LIMIT:
     case ELEMENT_ALLOW:
     case ELEMENT_DENY:
     case ELEMENT_FORK:
@@ -1243,6 +1711,29 @@ bus_config_parser_content (BusConfigParser   *parser,
         _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;
     }
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
@@ -1334,6 +1825,14 @@ bus_config_parser_steal_policy (BusConfigParser *parser)
   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>