2003-03-31 Havoc Pennington <hp@redhat.com>
[platform/upstream/dbus.git] / dbus / dbus-auth.c
index dca83ba..fd4f60d 100644 (file)
@@ -24,6 +24,8 @@
 #include "dbus-string.h"
 #include "dbus-list.h"
 #include "dbus-internals.h"
+#include "dbus-keyring.h"
+#include "dbus-sha.h"
 
 /* See doc/dbus-sasl-profile.txt */
 
  * @todo some SASL profiles require sending the empty string as a
  * challenge/response, but we don't currently allow that in our
  * protocol.
+ *
+ * @todo DBusAuth really needs to be rewritten as an explicit state
+ * machine. Right now it's too hard to prove to yourself by inspection
+ * that it works.
+ *
+ * @todo right now sometimes both ends will block waiting for input
+ * from the other end, e.g. if there's an error during
+ * DBUS_COOKIE_SHA1.
+ *
+ * @todo the cookie keyring needs to be cached globally not just
+ * per-auth (which raises threadsafety issues too)
+ * 
+ * @todo grep FIXME in dbus-auth.c
  */
 
 /**
@@ -132,9 +147,18 @@ struct DBusAuth
                                           *   as.
                                           */
   
-  DBusCredentials credentials;      /**< Credentials, fields may be -1 */
+  DBusCredentials credentials;      /**< Credentials read from socket,
+                                     * fields may be -1
+                                     */
 
   DBusCredentials authorized_identity; /**< Credentials that are authorized */
+
+  DBusCredentials desired_identity;    /**< Identity client has requested */
+  
+  DBusString context;               /**< Cookie scope */
+  DBusKeyring *keyring;             /**< Keyring for cookie mechanism. */
+  int cookie_id;                    /**< ID of cookie to use */
+  DBusString challenge;             /**< Challenge sent to client */
   
   unsigned int needed_memory : 1;   /**< We needed memory to continue since last
                                      * successful getting something done
@@ -145,6 +169,7 @@ struct DBusAuth
   unsigned int authenticated_pending_begin : 1;  /**< Authenticated once we get BEGIN */
   unsigned int already_got_mechanisms : 1;       /**< Client already got mech list */
   unsigned int already_asked_for_initial_response : 1; /**< Already sent a blank challenge to get an initial response */
+  unsigned int buffer_outstanding : 1; /**< Buffer is "checked out" for reading data into */
 };
 
 typedef struct
@@ -254,6 +279,13 @@ _dbus_auth_new (int size)
   auth->authorized_identity.pid = -1;
   auth->authorized_identity.uid = -1;
   auth->authorized_identity.gid = -1;
+
+  auth->desired_identity.pid = -1;
+  auth->desired_identity.uid = -1;
+  auth->desired_identity.gid = -1;
+  
+  auth->keyring = NULL;
+  auth->cookie_id = -1;
   
   /* note that we don't use the max string length feature,
    * because you can't use that feature if you're going to
@@ -263,28 +295,40 @@ _dbus_auth_new (int size)
    * overlong buffers in _dbus_auth_do_work().
    */
   
-  if (!_dbus_string_init (&auth->incoming, _DBUS_INT_MAX))
-    {
-      dbus_free (auth);
-      return NULL;
-    }
+  if (!_dbus_string_init (&auth->incoming))
+    goto enomem_0;
 
-  if (!_dbus_string_init (&auth->outgoing, _DBUS_INT_MAX))
-    {
-      _dbus_string_free (&auth->incoming);
-      dbus_free (auth);
-      return NULL;
-    }
-  
-  if (!_dbus_string_init (&auth->identity, _DBUS_INT_MAX))
-    {
-      _dbus_string_free (&auth->incoming);
-      _dbus_string_free (&auth->outgoing);
-      dbus_free (auth);
-      return NULL;
-    }
+  if (!_dbus_string_init (&auth->outgoing))
+    goto enomem_1;
+    
+  if (!_dbus_string_init (&auth->identity))
+    goto enomem_2;
+
+  if (!_dbus_string_init (&auth->context))
+    goto enomem_3;
+
+  if (!_dbus_string_init (&auth->challenge))
+    goto enomem_4;
+
+  /* default context if none is specified */
+  if (!_dbus_string_append (&auth->context, "org_freedesktop_general"))
+    goto enomem_5;
   
   return auth;
+
+ enomem_5:
+  _dbus_string_free (&auth->challenge);
+ enomem_4:
+  _dbus_string_free (&auth->context);
+ enomem_3:
+  _dbus_string_free (&auth->identity);
+ enomem_2:
+  _dbus_string_free (&auth->outgoing);
+ enomem_1:
+  _dbus_string_free (&auth->incoming);
+ enomem_0:
+  dbus_free (auth);
+  return NULL;
 }
 
 static void
@@ -295,9 +339,14 @@ shutdown_mech (DBusAuth *auth)
   auth->authenticated = FALSE;
   auth->already_asked_for_initial_response = FALSE;
   _dbus_string_set_length (&auth->identity, 0);
+  
   auth->authorized_identity.pid = -1;
   auth->authorized_identity.uid = -1;
   auth->authorized_identity.gid = -1;
+
+  auth->desired_identity.pid = -1;
+  auth->desired_identity.uid = -1;
+  auth->desired_identity.gid = -1;
   
   if (auth->mech != NULL)
     {
@@ -313,72 +362,603 @@ shutdown_mech (DBusAuth *auth)
     }
 }
 
+/* Returns TRUE but with an empty string hash if the
+ * cookie_id isn't known. As with all this code
+ * TRUE just means we had enough memory.
+ */
 static dbus_bool_t
-handle_server_data_stupid_test_mech (DBusAuth         *auth,
-                                     const DBusString *data)
+sha1_compute_hash (DBusAuth         *auth,
+                   int               cookie_id,
+                   const DBusString *server_challenge,
+                   const DBusString *client_challenge,
+                   DBusString       *hash)
 {
-  if (!_dbus_string_append (&auth->outgoing,
-                            "OK\r\n"))
+  DBusString cookie;
+  DBusString to_hash;
+  dbus_bool_t retval;
+  
+  _dbus_assert (auth->keyring != NULL);
+
+  retval = FALSE;
+  
+  if (!_dbus_string_init (&cookie))
     return FALSE;
 
-  auth->authenticated_pending_begin = TRUE;
+  if (!_dbus_keyring_get_hex_key (auth->keyring, cookie_id,
+                                  &cookie))
+    goto out_0;
+
+  if (_dbus_string_get_length (&cookie) == 0)
+    {
+      retval = TRUE;
+      goto out_0;
+    }
+
+  if (!_dbus_string_init (&to_hash))
+    goto out_0;
   
-  return TRUE;
+  if (!_dbus_string_copy (server_challenge, 0,
+                          &to_hash, _dbus_string_get_length (&to_hash)))
+    goto out_1;
+
+  if (!_dbus_string_append (&to_hash, ":"))
+    goto out_1;
+  
+  if (!_dbus_string_copy (client_challenge, 0,
+                          &to_hash, _dbus_string_get_length (&to_hash)))
+    goto out_1;
+
+  if (!_dbus_string_append (&to_hash, ":"))
+    goto out_1;
+
+  if (!_dbus_string_copy (&cookie, 0,
+                          &to_hash, _dbus_string_get_length (&to_hash)))
+    goto out_1;
+
+  if (!_dbus_sha_compute (&to_hash, hash))
+    goto out_1;
+  
+  retval = TRUE;
+
+ out_1:
+  _dbus_string_zero (&to_hash);
+  _dbus_string_free (&to_hash);
+ out_0:
+  _dbus_string_zero (&cookie);
+  _dbus_string_free (&cookie);
+  return retval;
 }
 
-static void
-handle_server_shutdown_stupid_test_mech (DBusAuth *auth)
+/** http://www.ietf.org/rfc/rfc2831.txt suggests at least 64 bits of
+ * entropy, we use 128. This is the number of bytes in the random
+ * challenge.
+ */
+#define N_CHALLENGE_BYTES (128/8)
+
+static dbus_bool_t
+sha1_handle_first_client_response (DBusAuth         *auth,
+                                   const DBusString *data)
 {
+  /* We haven't sent a challenge yet, we're expecting a desired
+   * username from the client.
+   */
+  DBusString tmp;
+  DBusString tmp2;
+  dbus_bool_t retval;
+  int old_len;
+  DBusError error;
+  
+  retval = FALSE;
 
+  _dbus_string_set_length (&auth->challenge, 0);
+  
+  if (_dbus_string_get_length (data) > 0)
+    {
+      if (_dbus_string_get_length (&auth->identity) > 0)
+        {
+          /* Tried to send two auth identities, wtf */
+          _dbus_verbose ("client tried to send auth identity, but we already have one\n");
+          return send_rejected (auth);
+        }
+      else
+        {
+          /* this is our auth identity */
+          if (!_dbus_string_copy (data, 0, &auth->identity, 0))
+            return FALSE;
+        }
+    }
+      
+  if (!_dbus_credentials_from_username (data, &auth->desired_identity))
+    {
+      _dbus_verbose ("Did not get a valid username from client\n");
+      return send_rejected (auth);
+    }
+      
+  if (!_dbus_string_init (&tmp))
+    return FALSE;
+
+  if (!_dbus_string_init (&tmp2))
+    {
+      _dbus_string_free (&tmp);
+      return FALSE;
+    }
+
+  old_len = _dbus_string_get_length (&auth->outgoing);
+  
+  /* we cache the keyring for speed, so here we drop it if it's the
+   * wrong one. FIXME caching the keyring here is useless since we use
+   * a different DBusAuth for every connection.
+   */
+  if (auth->keyring &&
+      !_dbus_keyring_is_for_user (auth->keyring,
+                                  data))
+    {
+      _dbus_keyring_unref (auth->keyring);
+      auth->keyring = NULL;
+    }
+  
+  if (auth->keyring == NULL)
+    {
+      DBusError error;
+
+      dbus_error_init (&error);
+      auth->keyring = _dbus_keyring_new_homedir (data,
+                                                 &auth->context,
+                                                 &error);
+
+      if (auth->keyring == NULL)
+        {
+          if (dbus_error_has_name (&error,
+                                   DBUS_ERROR_NO_MEMORY))
+            {
+              dbus_error_free (&error);
+              goto out;
+            }
+          else
+            {
+              _DBUS_ASSERT_ERROR_IS_SET (&error);
+              _dbus_verbose ("Error loading keyring: %s\n",
+                             error.message);
+              if (send_rejected (auth))
+                retval = TRUE; /* retval is only about mem */
+              dbus_error_free (&error);
+              goto out;
+            }
+        }
+      else
+        {
+          _dbus_assert (!dbus_error_is_set (&error));
+        }
+    }
+
+  _dbus_assert (auth->keyring != NULL);
+
+  dbus_error_init (&error);
+  auth->cookie_id = _dbus_keyring_get_best_key (auth->keyring, &error);
+  if (auth->cookie_id < 0)
+    {
+      _DBUS_ASSERT_ERROR_IS_SET (&error);
+      _dbus_verbose ("Could not get a cookie ID to send to client: %s\n",
+                     error.message);
+      if (send_rejected (auth))
+        retval = TRUE;
+      dbus_error_free (&error);
+      goto out;
+    }
+  else
+    {
+      _dbus_assert (!dbus_error_is_set (&error));
+    }
+
+  if (!_dbus_string_copy (&auth->context, 0,
+                          &tmp2, _dbus_string_get_length (&tmp2)))
+    goto out;
+
+  if (!_dbus_string_append (&tmp2, " "))
+    goto out;
+
+  if (!_dbus_string_append_int (&tmp2, auth->cookie_id))
+    goto out;
+
+  if (!_dbus_string_append (&tmp2, " "))
+    goto out;  
+  
+  if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES))
+    goto out;
+
+  _dbus_string_set_length (&auth->challenge, 0);
+  if (!_dbus_string_hex_encode (&tmp, 0, &auth->challenge, 0))
+    goto out;
+  
+  if (!_dbus_string_hex_encode (&tmp, 0, &tmp2,
+                                _dbus_string_get_length (&tmp2)))
+    goto out;
+
+  if (!_dbus_string_append (&auth->outgoing,
+                            "DATA "))
+    goto out;
+  
+  if (!_dbus_string_base64_encode (&tmp2, 0, &auth->outgoing,
+                                   _dbus_string_get_length (&auth->outgoing)))
+    goto out;
+
+  if (!_dbus_string_append (&auth->outgoing,
+                            "\r\n"))
+    goto out;
+      
+  retval = TRUE;
+  
+ out:
+  _dbus_string_zero (&tmp);
+  _dbus_string_free (&tmp);
+  _dbus_string_zero (&tmp2);
+  _dbus_string_free (&tmp2);
+  if (!retval)
+    _dbus_string_set_length (&auth->outgoing, old_len);
+  return retval;
 }
 
 static dbus_bool_t
-handle_client_data_stupid_test_mech (DBusAuth         *auth,
-                                     const DBusString *data)
+sha1_handle_second_client_response (DBusAuth         *auth,
+                                    const DBusString *data)
 {
+  /* We are expecting a response which is the hex-encoded client
+   * challenge, space, then SHA-1 hash of the concatenation of our
+   * challenge, ":", client challenge, ":", secret key, all
+   * hex-encoded.
+   */
+  int i;
+  DBusString client_challenge;
+  DBusString client_hash;
+  dbus_bool_t retval;
+  DBusString correct_hash;
   
-  return TRUE;
+  retval = FALSE;
+  
+  if (!_dbus_string_find_blank (data, 0, &i))
+    {
+      _dbus_verbose ("no space separator in client response\n");
+      return send_rejected (auth);
+    }
+  
+  if (!_dbus_string_init (&client_challenge))
+    goto out_0;
+
+  if (!_dbus_string_init (&client_hash))
+    goto out_1;  
+
+  if (!_dbus_string_copy_len (data, 0, i, &client_challenge,
+                              0))
+    goto out_2;
+
+  _dbus_string_skip_blank (data, i, &i);
+  
+  if (!_dbus_string_copy_len (data, i,
+                              _dbus_string_get_length (data) - i,
+                              &client_hash,
+                              0))
+    goto out_2;
+
+  if (_dbus_string_get_length (&client_challenge) == 0 ||
+      _dbus_string_get_length (&client_hash) == 0)
+    {
+      _dbus_verbose ("zero-length client challenge or hash\n");
+      if (send_rejected (auth))
+        retval = TRUE;
+      goto out_2;
+    }
+
+  if (!_dbus_string_init (&correct_hash))
+    goto out_2;
+
+  if (!sha1_compute_hash (auth, auth->cookie_id,
+                          &auth->challenge, 
+                          &client_challenge,
+                          &correct_hash))
+    goto out_3;
+
+  /* if cookie_id was invalid, then we get an empty hash */
+  if (_dbus_string_get_length (&correct_hash) == 0)
+    {
+      if (send_rejected (auth))
+        retval = TRUE;
+      goto out_3;
+    }
+  
+  if (!_dbus_string_equal (&client_hash, &correct_hash))
+    {
+      if (send_rejected (auth))
+        retval = TRUE;
+      goto out_3;
+    }
+      
+  if (!_dbus_string_append (&auth->outgoing,
+                            "OK\r\n"))
+    goto out_3;
+
+  _dbus_verbose ("authenticated client with UID %d using DBUS_COOKIE_SHA1\n",
+                 auth->desired_identity.uid);
+  
+  auth->authorized_identity = auth->desired_identity;
+  auth->authenticated_pending_begin = TRUE;
+  retval = TRUE;
+  
+ out_3:
+  _dbus_string_zero (&correct_hash);
+  _dbus_string_free (&correct_hash);
+ out_2:
+  _dbus_string_zero (&client_hash);
+  _dbus_string_free (&client_hash);
+ out_1:
+  _dbus_string_free (&client_challenge);
+ out_0:
+  return retval;
 }
 
-static void
-handle_client_shutdown_stupid_test_mech (DBusAuth *auth)
+static dbus_bool_t
+handle_server_data_cookie_sha1_mech (DBusAuth         *auth,
+                                     const DBusString *data)
 {
+  if (auth->cookie_id < 0)
+    return sha1_handle_first_client_response (auth, data);
+  else
+    return sha1_handle_second_client_response (auth, data);
+}
 
+static void
+handle_server_shutdown_cookie_sha1_mech (DBusAuth *auth)
+{
+  auth->cookie_id = -1;  
+  _dbus_string_set_length (&auth->challenge, 0);
 }
 
-/* the stupid test mech is a base64-encoded string;
- * all the inefficiency, none of the security!
- */
 static dbus_bool_t
-handle_encode_stupid_test_mech (DBusAuth         *auth,
-                                const DBusString *plaintext,
-                                DBusString       *encoded)
+handle_client_initial_response_cookie_sha1_mech (DBusAuth   *auth,
+                                                 DBusString *response)
 {
-  if (!_dbus_string_base64_encode (plaintext, 0, encoded,
-                                   _dbus_string_get_length (encoded)))
-    return FALSE;
+  const DBusString *username;
+  dbus_bool_t retval;
+
+  retval = FALSE;
+
+  if (!_dbus_user_info_from_current_process (&username,
+                                             NULL, NULL))
+    goto out_0;
+
+  if (!_dbus_string_base64_encode (username, 0,
+                                   response,
+                                   _dbus_string_get_length (response)))
+    goto out_0;
+
+  retval = TRUE;
   
-  return TRUE;
+ out_0:
+  return retval;
 }
 
+/* FIXME if we send the server an error, right now both sides
+ * just hang. Server has to reject on getting an error, or
+ * client has to cancel. Should be in the spec.
+ */
 static dbus_bool_t
-handle_decode_stupid_test_mech (DBusAuth         *auth,
-                                const DBusString *encoded,
-                                DBusString       *plaintext)
+handle_client_data_cookie_sha1_mech (DBusAuth         *auth,
+                                     const DBusString *data)
 {
-  if (!_dbus_string_base64_decode (encoded, 0, plaintext,
-                                   _dbus_string_get_length (plaintext)))
-    return FALSE;
+  /* The data we get from the server should be the cookie context
+   * name, the cookie ID, and the server challenge, separated by
+   * spaces. We send back our challenge string and the correct hash.
+   */
+  dbus_bool_t retval;
+  DBusString context;
+  DBusString cookie_id_str;
+  DBusString server_challenge;
+  DBusString client_challenge;
+  DBusString correct_hash;
+  DBusString tmp;
+  int i, j;
+  long val;
+  int old_len;
   
-  return TRUE;
+  retval = FALSE;                 
+  
+  if (!_dbus_string_find_blank (data, 0, &i))
+    {
+      if (_dbus_string_append (&auth->outgoing,
+                               "ERROR \"Server did not send context/ID/challenge properly\"\r\n"))
+        retval = TRUE;
+      goto out_0;
+    }
+
+  if (!_dbus_string_init (&context))
+    goto out_0;
+
+  if (!_dbus_string_copy_len (data, 0, i,
+                              &context, 0))
+    goto out_1;
+  
+  _dbus_string_skip_blank (data, i, &i);
+  if (!_dbus_string_find_blank (data, i, &j))
+    {
+      if (_dbus_string_append (&auth->outgoing,
+                               "ERROR \"Server did not send context/ID/challenge properly\"\r\n"))
+        retval = TRUE;
+      goto out_1;
+    }
+
+  if (!_dbus_string_init (&cookie_id_str))
+    goto out_1;
+  
+  if (!_dbus_string_copy_len (data, i, j - i,
+                              &cookie_id_str, 0))
+    goto out_2;  
+
+  if (!_dbus_string_init (&server_challenge))
+    goto out_2;
+
+  i = j;
+  _dbus_string_skip_blank (data, i, &i);
+  j = _dbus_string_get_length (data);
+
+  if (!_dbus_string_copy_len (data, i, j - i,
+                              &server_challenge, 0))
+    goto out_3;
+
+  if (!_dbus_keyring_validate_context (&context))
+    {
+      if (_dbus_string_append (&auth->outgoing,
+                               "ERROR \"Server sent invalid cookie context\"\r\n"))
+        retval = TRUE;
+      goto out_3;
+    }
+
+  if (!_dbus_string_parse_int (&cookie_id_str, 0, &val, NULL))
+    {
+      if (_dbus_string_append (&auth->outgoing,
+                               "ERROR \"Could not parse cookie ID as an integer\"\r\n"))
+        retval = TRUE;
+      goto out_3;
+    }
+
+  if (_dbus_string_get_length (&server_challenge) == 0)
+    {
+      if (_dbus_string_append (&auth->outgoing,
+                               "ERROR \"Empty server challenge string\"\r\n"))
+        retval = TRUE;
+      goto out_3;
+    }
+
+  if (auth->keyring == NULL)
+    {
+      DBusError error;
+
+      dbus_error_init (&error);
+      auth->keyring = _dbus_keyring_new_homedir (NULL,
+                                                 &context,
+                                                 &error);
+
+      if (auth->keyring == NULL)
+        {
+          if (dbus_error_has_name (&error,
+                                   DBUS_ERROR_NO_MEMORY))
+            {
+              dbus_error_free (&error);
+              goto out_3;
+            }
+          else
+            {
+              _DBUS_ASSERT_ERROR_IS_SET (&error);
+
+              _dbus_verbose ("Error loading keyring: %s\n",
+                             error.message);
+              
+              if (_dbus_string_append (&auth->outgoing,
+                                       "ERROR \"Could not load cookie file\"\r\n"))
+                retval = TRUE; /* retval is only about mem */
+              
+              dbus_error_free (&error);
+              goto out_3;
+            }
+        }
+      else
+        {
+          _dbus_assert (!dbus_error_is_set (&error));
+        }
+    }
+  
+  _dbus_assert (auth->keyring != NULL);
+  
+  if (!_dbus_string_init (&tmp))
+    goto out_3;
+  
+  if (!_dbus_generate_random_bytes (&tmp, N_CHALLENGE_BYTES))
+    goto out_4;
+
+  if (!_dbus_string_init (&client_challenge))
+    goto out_4;
+
+  if (!_dbus_string_hex_encode (&tmp, 0, &client_challenge, 0))
+    goto out_5;
+
+  if (!_dbus_string_init (&correct_hash))
+    goto out_6;
+  
+  if (!sha1_compute_hash (auth, val,
+                          &server_challenge,
+                          &client_challenge,
+                          &correct_hash))
+    goto out_6;
+
+  if (_dbus_string_get_length (&correct_hash) == 0)
+    {
+      /* couldn't find the cookie ID or something */
+      if (_dbus_string_append (&auth->outgoing,
+                               "ERROR \"Don't have the requested cookie ID\"\r\n"))
+        retval = TRUE;
+      goto out_6;
+    }
+  
+  _dbus_string_set_length (&tmp, 0);
+  
+  if (!_dbus_string_copy (&client_challenge, 0, &tmp,
+                          _dbus_string_get_length (&tmp)))
+    goto out_6;
+
+  if (!_dbus_string_append (&tmp, " "))
+    goto out_6;
+
+  if (!_dbus_string_copy (&correct_hash, 0, &tmp,
+                          _dbus_string_get_length (&tmp)))
+    goto out_6;
+
+  old_len = _dbus_string_get_length (&auth->outgoing);
+  if (!_dbus_string_append (&auth->outgoing, "DATA "))
+    goto out_6;
+
+  if (!_dbus_string_base64_encode (&tmp, 0,
+                                   &auth->outgoing,
+                                   _dbus_string_get_length (&auth->outgoing)))
+    {
+      _dbus_string_set_length (&auth->outgoing, old_len);
+      goto out_6;
+    }
+
+  if (!_dbus_string_append (&auth->outgoing, "\r\n"))
+    {
+      _dbus_string_set_length (&auth->outgoing, old_len);
+      goto out_6;
+    }
+  
+  retval = TRUE;
+
+ out_6:
+  _dbus_string_zero (&correct_hash);
+  _dbus_string_free (&correct_hash);
+ out_5:
+  _dbus_string_free (&client_challenge);
+ out_4:
+  _dbus_string_zero (&tmp);
+  _dbus_string_free (&tmp);
+ out_3:
+  _dbus_string_free (&server_challenge);
+ out_2:
+  _dbus_string_free (&cookie_id_str);
+ out_1:
+  _dbus_string_free (&context);
+ out_0:
+  return retval;
+}
+
+static void
+handle_client_shutdown_cookie_sha1_mech (DBusAuth *auth)
+{
+  auth->cookie_id = -1;  
+  _dbus_string_set_length (&auth->challenge, 0);
 }
 
 static dbus_bool_t
 handle_server_data_external_mech (DBusAuth         *auth,
                                   const DBusString *data)
 {
-  DBusCredentials desired_identity;
-
   if (auth->credentials.uid < 0)
     {
       _dbus_verbose ("no credentials, mechanism EXTERNAL can't authenticate\n");
@@ -416,9 +996,9 @@ handle_server_data_external_mech (DBusAuth         *auth,
         return FALSE;
     }
 
-  desired_identity.pid = -1;
-  desired_identity.uid = -1;
-  desired_identity.gid = -1;
+  auth->desired_identity.pid = -1;
+  auth->desired_identity.uid = -1;
+  auth->desired_identity.gid = -1;
   
   /* If auth->identity is still empty here, then client
    * responded with an empty string after we poked it for
@@ -427,37 +1007,37 @@ handle_server_data_external_mech (DBusAuth         *auth,
    */
   if (_dbus_string_get_length (&auth->identity) == 0)
     {
-      desired_identity.uid = auth->credentials.uid;
+      auth->desired_identity.uid = auth->credentials.uid;
     }
   else
     {
       if (!_dbus_credentials_from_uid_string (&auth->identity,
-                                              &desired_identity))
+                                              &auth->desired_identity))
         {
           _dbus_verbose ("could not get credentials from uid string\n");
           return send_rejected (auth);
         }
     }
 
-  if (desired_identity.uid < 0)
+  if (auth->desired_identity.uid < 0)
     {
-      _dbus_verbose ("desired UID %d is no good\n", desired_identity.uid);
+      _dbus_verbose ("desired UID %d is no good\n", auth->desired_identity.uid);
       return send_rejected (auth);
     }
   
-  if (_dbus_credentials_match (&desired_identity,
+  if (_dbus_credentials_match (&auth->desired_identity,
                                &auth->credentials))
     {
-      /* client has authenticated */
-      _dbus_verbose ("authenticated client with UID %d matching socket credentials UID %d\n",
-                     desired_identity.uid,
-                     auth->credentials.uid);
-      
+      /* client has authenticated */      
       if (!_dbus_string_append (&auth->outgoing,
                                 "OK\r\n"))
         return FALSE;
 
-      auth->authorized_identity.uid = desired_identity.uid;
+      _dbus_verbose ("authenticated client with UID %d matching socket credentials UID %d\n",
+                     auth->desired_identity.uid,
+                     auth->credentials.uid);
+      
+      auth->authorized_identity.uid = auth->desired_identity.uid;
       
       auth->authenticated_pending_begin = TRUE;
       
@@ -467,7 +1047,7 @@ handle_server_data_external_mech (DBusAuth         *auth,
     {
       _dbus_verbose ("credentials uid=%d gid=%d do not allow uid=%d gid=%d\n",
                      auth->credentials.uid, auth->credentials.gid,
-                     desired_identity.uid, desired_identity.gid);
+                     auth->desired_identity.uid, auth->desired_identity.gid);
       return send_rejected (auth);
     }
 }
@@ -489,7 +1069,7 @@ handle_client_initial_response_external_mech (DBusAuth         *auth,
    */
   DBusString plaintext;
 
-  if (!_dbus_string_init (&plaintext, _DBUS_INT_MAX))
+  if (!_dbus_string_init (&plaintext))
     return FALSE;
   
   if (!_dbus_string_append_our_uid (&plaintext))
@@ -542,17 +1122,14 @@ all_mechanisms[] = {
     handle_client_data_external_mech,
     NULL, NULL,
     handle_client_shutdown_external_mech },
-  /* Obviously this has to die for production use */
-  { "DBUS_STUPID_TEST_MECH",
-    handle_server_data_stupid_test_mech,
-    handle_encode_stupid_test_mech,
-    handle_decode_stupid_test_mech,
-    handle_server_shutdown_stupid_test_mech,
-    NULL,
-    handle_client_data_stupid_test_mech,
-    handle_encode_stupid_test_mech,
-    handle_decode_stupid_test_mech,
-    handle_client_shutdown_stupid_test_mech },
+  { "DBUS_COOKIE_SHA1",
+    handle_server_data_cookie_sha1_mech,
+    NULL, NULL,
+    handle_server_shutdown_cookie_sha1_mech,
+    handle_client_initial_response_cookie_sha1_mech,
+    handle_client_data_cookie_sha1_mech,
+    NULL, NULL,
+    handle_client_shutdown_cookie_sha1_mech },
   { NULL, NULL }
 };
 
@@ -582,7 +1159,7 @@ send_rejected (DBusAuth *auth)
   DBusAuthServer *server_auth;
   int i;
   
-  if (!_dbus_string_init (&command, _DBUS_INT_MAX))
+  if (!_dbus_string_init (&command))
     return FALSE;
   
   if (!_dbus_string_append (&command,
@@ -615,6 +1192,8 @@ send_rejected (DBusAuth *auth)
   _dbus_assert (DBUS_AUTH_IS_SERVER (auth));
   server_auth = DBUS_AUTH_SERVER (auth);
   server_auth->failures += 1;
+
+  _dbus_string_free (&command);
   
   return TRUE;
 
@@ -654,22 +1233,22 @@ process_auth (DBusAuth         *auth,
       
       _dbus_string_find_blank (args, 0, &i);
 
-      if (!_dbus_string_init (&mech, _DBUS_INT_MAX))
+      if (!_dbus_string_init (&mech))
         return FALSE;
 
-      if (!_dbus_string_init (&base64_response, _DBUS_INT_MAX))
+      if (!_dbus_string_init (&base64_response))
         {
           _dbus_string_free (&mech);
           return FALSE;
         }
-
-      if (!_dbus_string_init (&decoded_response, _DBUS_INT_MAX))
+      
+      if (!_dbus_string_init (&decoded_response))
         {
           _dbus_string_free (&mech);
           _dbus_string_free (&base64_response);
           return FALSE;
         }
-      
+
       if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
         goto failed;
 
@@ -679,7 +1258,7 @@ process_auth (DBusAuth         *auth,
       if (!_dbus_string_base64_decode (&base64_response, 0,
                                        &decoded_response, 0))
         goto failed;
-
+      
       auth->mech = find_mech (&mech);
       if (auth->mech != NULL)
         {
@@ -695,7 +1274,7 @@ process_auth (DBusAuth         *auth,
         {
           /* Unsupported mechanism */
           if (!send_rejected (auth))
-            return FALSE;
+            goto failed;
         }
 
       _dbus_string_free (&mech);      
@@ -750,7 +1329,7 @@ process_data_server (DBusAuth         *auth,
     {
       DBusString decoded;
 
-      if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
+      if (!_dbus_string_init (&decoded))
         return FALSE;
 
       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
@@ -758,6 +1337,12 @@ process_data_server (DBusAuth         *auth,
           _dbus_string_free (&decoded);
           return FALSE;
         }
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+      if (_dbus_string_validate_ascii (&decoded, 0,
+                                       _dbus_string_get_length (&decoded)))
+        _dbus_verbose ("data: '%s'\n", _dbus_string_get_const_data (&decoded));
+#endif
       
       if (!(* auth->mech->server_data_func) (auth, &decoded))
         {
@@ -799,7 +1384,7 @@ get_word (const DBusString *str,
   
   if (i > *start)
     {
-      if (!_dbus_string_copy_len (str, *start, i, word, 0))
+      if (!_dbus_string_copy_len (str, *start, i - *start, word, 0))
         return FALSE;
       
       *start = i;
@@ -827,7 +1412,7 @@ record_mechanisms (DBusAuth         *auth,
       DBusString m;
       const DBusAuthMechanismHandler *mech;
       
-      if (!_dbus_string_init (&m, _DBUS_INT_MAX))
+      if (!_dbus_string_init (&m))
         goto nomem;
       
       if (!get_word (args, &next, &m))
@@ -855,11 +1440,8 @@ record_mechanisms (DBusAuth         *auth,
         }
       else
         {
-          const char *s;
-
-          _dbus_string_get_const_data (&m, &s);
           _dbus_verbose ("Server offered mechanism \"%s\" that we don't know how to use\n",
-                         s);
+                         _dbus_string_get_const_data (&m));
         }
 
       _dbus_string_free (&m);
@@ -886,7 +1468,7 @@ client_try_next_mechanism (DBusAuth *auth)
 
   mech = DBUS_AUTH_CLIENT (auth)->mechs_to_try->data;
 
-  if (!_dbus_string_init (&auth_command, _DBUS_INT_MAX))
+  if (!_dbus_string_init (&auth_command))
     return FALSE;
       
   if (!_dbus_string_append (&auth_command,
@@ -984,7 +1566,6 @@ process_ok (DBusAuth         *auth,
   return TRUE;
 }
 
-
 static dbus_bool_t
 process_data_client (DBusAuth         *auth,
                      const DBusString *command,
@@ -994,7 +1575,7 @@ process_data_client (DBusAuth         *auth,
     {
       DBusString decoded;
 
-      if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
+      if (!_dbus_string_init (&decoded))
         return FALSE;
 
       if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
@@ -1002,6 +1583,15 @@ process_data_client (DBusAuth         *auth,
           _dbus_string_free (&decoded);
           return FALSE;
         }
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+      if (_dbus_string_validate_ascii (&decoded, 0,
+                                       _dbus_string_get_length (&decoded)))
+        {
+          _dbus_verbose ("data: '%s'\n",
+                         _dbus_string_get_const_data (&decoded));
+        }
+#endif
       
       if (!(* auth->mech->client_data_func) (auth, &decoded))
         {
@@ -1059,14 +1649,15 @@ process_command (DBusAuth *auth)
   if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
     return FALSE;
   
-  if (!_dbus_string_init (&command, _DBUS_INT_MAX))
+  if (!_dbus_string_init (&command))
     {
       auth->needed_memory = TRUE;
       return FALSE;
     }
 
-  if (!_dbus_string_init (&args, _DBUS_INT_MAX))
+  if (!_dbus_string_init (&args))
     {
+      _dbus_string_free (&command);
       auth->needed_memory = TRUE;
       return FALSE;
     }
@@ -1093,11 +1684,7 @@ process_command (DBusAuth *auth)
         goto next_command;
     }
   
-  {
-    const char *q;
-    _dbus_string_get_const_data (&command, &q);
-    _dbus_verbose ("got command \"%s\"\n", q);
-  }
+  _dbus_verbose ("got command \"%s\"\n", _dbus_string_get_const_data (&command));
   
   _dbus_string_find_blank (&command, 0, &i);
   _dbus_string_skip_blank (&command, i, &j);
@@ -1264,6 +1851,11 @@ _dbus_auth_unref (DBusAuth *auth)
           _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
         }
 
+      if (auth->keyring)
+        _dbus_keyring_unref (auth->keyring);
+
+      _dbus_string_free (&auth->context);
+      _dbus_string_free (&auth->challenge);
       _dbus_string_free (&auth->identity);
       _dbus_string_free (&auth->incoming);
       _dbus_string_free (&auth->outgoing);
@@ -1379,6 +1971,9 @@ void
 _dbus_auth_bytes_sent (DBusAuth *auth,
                        int       bytes_sent)
 {
+  _dbus_verbose ("Sent %d bytes of: %s\n", bytes_sent,
+                 _dbus_string_get_const_data (&auth->outgoing));
+  
   _dbus_string_delete (&auth->outgoing,
                        0, bytes_sent);
   
@@ -1388,35 +1983,40 @@ _dbus_auth_bytes_sent (DBusAuth *auth,
 }
 
 /**
- * Stores bytes received from the peer we're conversing with.
+ * Get a buffer to be used for reading bytes from the peer we're conversing
+ * with. Bytes should be appended to this buffer.
  *
  * @param auth the auth conversation
- * @param str the received bytes.
- * @returns #FALSE if not enough memory to store the bytes or we were already authenticated.
+ * @param buffer return location for buffer to append bytes to
  */
-dbus_bool_t
-_dbus_auth_bytes_received (DBusAuth   *auth,
-                           const DBusString *str)
+void
+_dbus_auth_get_buffer (DBusAuth     *auth,
+                       DBusString **buffer)
 {
   _dbus_assert (auth != NULL);
-  _dbus_assert (str != NULL);
+  _dbus_assert (!auth->buffer_outstanding);
   
-  if (DBUS_AUTH_IN_END_STATE (auth))
-    return FALSE;
+  *buffer = &auth->incoming;
 
-  auth->needed_memory = FALSE;
-  
-  if (!_dbus_string_copy (str, 0,
-                          &auth->incoming,
-                          _dbus_string_get_length (&auth->incoming)))
-    {
-      auth->needed_memory = TRUE;
-      return FALSE;
-    }
+  auth->buffer_outstanding = TRUE;
+}
 
-  _dbus_auth_do_work (auth);
-  
-  return TRUE;
+/**
+ * Returns a buffer with new data read into it.
+ *
+ * @param auth the auth conversation
+ * @param buffer the buffer being returned
+ * @param bytes_read number of new bytes added
+ */
+void
+_dbus_auth_return_buffer (DBusAuth               *auth,
+                          DBusString             *buffer,
+                          int                     bytes_read)
+{
+  _dbus_assert (buffer == &auth->incoming);
+  _dbus_assert (auth->buffer_outstanding);
+
+  auth->buffer_outstanding = FALSE;
 }
 
 /**
@@ -1426,22 +2026,32 @@ _dbus_auth_bytes_received (DBusAuth   *auth,
  * succeeded.
  *
  * @param auth the auth conversation
- * @param str string to append the unused bytes to
- * @returns #FALSE if not enough memory to return the bytes
+ * @param str return location for pointer to string of unused bytes
  */
-dbus_bool_t
-_dbus_auth_get_unused_bytes (DBusAuth   *auth,
-                             DBusString *str)
+void
+_dbus_auth_get_unused_bytes (DBusAuth           *auth,
+                             const DBusString **str)
 {
   if (!DBUS_AUTH_IN_END_STATE (auth))
-    return FALSE;
-  
-  if (!_dbus_string_move (&auth->incoming,
-                          0, str,
-                          _dbus_string_get_length (str)))
-    return FALSE;
+    return;
 
-  return TRUE;
+  *str = &auth->incoming;
+}
+
+
+/**
+ * Gets rid of unused bytes returned by _dbus_auth_get_unused_bytes()
+ * after we've gotten them and successfully moved them elsewhere.
+ *
+ * @param auth the auth conversation
+ */
+void
+_dbus_auth_delete_unused_bytes (DBusAuth *auth)
+{
+  if (!DBUS_AUTH_IN_END_STATE (auth))
+    return;
+
+  _dbus_string_set_length (&auth->incoming, 0);
 }
 
 /**
@@ -1603,6 +2213,22 @@ _dbus_auth_get_identity (DBusAuth               *auth,
     }
 }
 
+/**
+ * Sets the "authentication context" which scopes cookies
+ * with the DBUS_COOKIE_SHA1 auth mechanism for example.
+ *
+ * @param auth the auth conversation
+ * @param context the context
+ * @returns #FALSE if no memory
+ */
+dbus_bool_t
+_dbus_auth_set_context (DBusAuth               *auth,
+                        const DBusString       *context)
+{
+  return _dbus_string_replace_len (context, 0, _dbus_string_get_length (context),
+                                   &auth->context, 0, _dbus_string_get_length (context));
+}
+
 /** @} */
 
 #ifdef DBUS_BUILD_TESTS
@@ -1618,12 +2244,12 @@ process_test_subdir (const DBusString          *test_base_dir,
   DBusString filename;
   DBusDirIter *dir;
   dbus_bool_t retval;
-  DBusResultCode result;
+  DBusError error;
 
   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);
@@ -1636,28 +2262,28 @@ 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");
-  
-  dir = _dbus_directory_open (&test_directory, &result);
+
+  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_result_to_string (result));
+      _dbus_warn ("Could not open %s: %s\n",
+                  _dbus_string_get_const_data (&test_directory),
+                  error.message);
+      dbus_error_free (&error);
       goto failed;
     }
 
   printf ("Testing:\n");
   
-  result = DBUS_RESULT_SUCCESS;
  next:
-  while (_dbus_directory_get_next_file (dir, &filename, &result))
+  while (_dbus_directory_get_next_file (dir, &filename, &error))
     {
       DBusString full_path;
       
-      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))
@@ -1668,18 +2294,13 @@ process_test_subdir (const DBusString          *test_base_dir,
 
       if (!_dbus_string_ends_with_c_str (&filename, ".auth-script"))
         {
-          const char *filename_c;
-          _dbus_string_get_const_data (&filename, &filename_c);
           _dbus_verbose ("Skipping non-.auth-script 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));
       
       if (!_dbus_auth_script_run (&full_path))
         {
@@ -1690,12 +2311,11 @@ process_test_subdir (const DBusString          *test_base_dir,
         _dbus_string_free (&full_path);
     }
 
-  if (result != DBUS_RESULT_SUCCESS)
+  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, dbus_result_to_string (result));
+                  _dbus_string_get_const_data (&test_directory), error.message);
+      dbus_error_free (&error);
       goto failed;
     }