2002-12-25 Havoc Pennington <hp@pobox.com>
authorHavoc Pennington <hp@redhat.com>
Wed, 25 Dec 2002 18:00:10 +0000 (18:00 +0000)
committerHavoc Pennington <hp@redhat.com>
Wed, 25 Dec 2002 18:00:10 +0000 (18:00 +0000)
* doc/dbus-sasl-profile.txt: docs on the authentication protocol,
it is a simple protocol that just maps directly to SASL.

* dbus/dbus-auth.h, dbus/dbus-auth.c: authentication protocol
initial implementation, not actually used yet.

* dbus/dbus-string.c (_dbus_string_find): new function
(_dbus_string_equal): new function
(_dbus_string_base64_encode): new function
(_dbus_string_base64_decode): new function

ChangeLog
dbus/Makefile.am
dbus/dbus-auth.c [new file with mode: 0644]
dbus/dbus-auth.h [new file with mode: 0644]
dbus/dbus-string.c
dbus/dbus-string.h
dbus/dbus-test.c
doc/dbus-sasl-profile.txt [new file with mode: 0644]

index a3dfab3..c093e47 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2002-12-25  Havoc Pennington  <hp@pobox.com>
+
+       * doc/dbus-sasl-profile.txt: docs on the authentication protocol, 
+       it is a simple protocol that just maps directly to SASL.
+
+       * dbus/dbus-auth.h, dbus/dbus-auth.c: authentication protocol
+       initial implementation, not actually used yet.
+       
+       * dbus/dbus-string.c (_dbus_string_find): new function
+       (_dbus_string_equal): new function
+       (_dbus_string_base64_encode): new function
+       (_dbus_string_base64_decode): new function
+
 2002-12-25  Anders Carlsson  <andersca@codefactory.se>
 
        * dbus/Makefile.am:
index ba968ce..6b8a0ff 100644 (file)
@@ -17,6 +17,8 @@ dbusinclude_HEADERS=                          \
        dbus-types.h
 
 libdbus_1_la_SOURCES=                          \
+       dbus-auth.c                             \
+       dbus-auth.h                             \
        dbus-connection.c                       \
        dbus-connection-internal.h              \
        dbus-errors.c                           \
diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c
new file mode 100644 (file)
index 0000000..b2fca83
--- /dev/null
@@ -0,0 +1,1215 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* dbus-auth.c Authentication
+ *
+ * Copyright (C) 2002  Red Hat Inc.
+ *
+ * Licensed under the Academic Free License version 1.2
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#include "dbus-auth.h"
+#include "dbus-string.h"
+#include "dbus-list.h"
+#include "dbus-internals.h"
+
+/* See doc/dbus-sasl-profile.txt */
+
+/**
+ * @defgroup DBusAuth Authentication
+ * @ingroup  DBusInternals
+ * @brief DBusAuth object
+ *
+ * DBusAuth manages the authentication negotiation when a connection
+ * is first established, and also manage any encryption used over a
+ * connection.
+ *
+ * The file doc/dbus-sasl-profile.txt documents the network protocol
+ * used for authentication.
+ */
+
+/**
+ * @defgroup DBusAuthInternals Authentication implementation details
+ * @ingroup  DBusInternals
+ * @brief DBusAuth implementation details
+ *
+ * Private details of authentication code.
+ *
+ * @{
+ */
+
+/**
+ * Processes a command. Returns whether we had enough memory to
+ * complete the operation.
+ */
+typedef dbus_bool_t (* DBusProcessAuthCommandFunction) (DBusAuth         *auth,
+                                                        const DBusString *command,
+                                                        const DBusString *args);
+
+typedef struct
+{
+  const char *command;
+  DBusProcessAuthCommandFunction func;
+} DBusAuthCommandHandler;
+
+/**
+ * This function processes a block of data received from the peer.
+ * i.e. handles a DATA command.
+ */
+typedef dbus_bool_t (* DBusAuthDataFunction)     (DBusAuth         *auth,
+                                                  const DBusString *data);
+
+/**
+ * This function encodes a block of data from the peer.
+ */
+typedef dbus_bool_t (* DBusAuthEncodeFunction)   (DBusAuth         *auth,
+                                                  const DBusString *data,
+                                                  DBusString       *encoded);
+
+/**
+ * This function decodes a block of data from the peer.
+ */
+typedef dbus_bool_t (* DBusAuthDecodeFunction)   (DBusAuth         *auth,
+                                                  const DBusString *data,
+                                                  DBusString       *decoded);
+
+/**
+ * This function is called when the mechanism is abandoned.
+ */
+typedef void        (* DBusAuthShutdownFunction) (DBusAuth       *auth);
+
+typedef struct
+{
+  const char *mechanism;
+  DBusAuthDataFunction server_data_func;
+  DBusAuthEncodeFunction server_encode_func;
+  DBusAuthDecodeFunction server_decode_func;
+  DBusAuthShutdownFunction server_shutdown_func;
+  DBusAuthDataFunction client_data_func;
+  DBusAuthEncodeFunction client_encode_func;
+  DBusAuthDecodeFunction client_decode_func;
+  DBusAuthShutdownFunction client_shutdown_func;
+} DBusAuthMechanismHandler;
+
+/**
+ * Internal members of DBusAuth.
+ */
+struct DBusAuth
+{
+  int refcount;           /**< reference count */
+
+  DBusString incoming;    /**< Incoming data buffer */
+  DBusString outgoing;    /**< Outgoing data buffer */
+  
+  const DBusAuthCommandHandler *handlers; /**< Handlers for commands */
+
+  const DBusAuthMechanismHandler *mech;   /**< Current auth mechanism */
+  
+  unsigned int needed_memory : 1;   /**< We needed memory to continue since last
+                                     * successful getting something done
+                                     */
+  unsigned int need_disconnect : 1; /**< We've given up, time to disconnect */
+  unsigned int authenticated : 1;   /**< We are authenticated */
+  unsigned int authenticated_pending_output : 1; /**< Authenticated once we clear outgoing buffer */
+  unsigned int authenticated_pending_begin : 1;  /**< Authenticated once we get BEGIN */
+  unsigned int already_got_mechanisms : 1;       /**< Client already got mech list */
+};
+
+typedef struct
+{
+  DBusAuth base;
+
+  DBusList *mechs_to_try;
+  
+} DBusAuthClient;
+
+typedef struct
+{
+  DBusAuth base;
+
+} DBusAuthServer;
+
+static dbus_bool_t process_auth         (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_cancel       (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_begin        (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_data_server  (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_error_server (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_mechanisms   (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_rejected     (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_ok           (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_data_client  (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+static dbus_bool_t process_error_client (DBusAuth         *auth,
+                                         const DBusString *command,
+                                         const DBusString *args);
+
+
+static DBusAuthCommandHandler
+server_handlers[] = {
+  { "AUTH", process_auth },
+  { "CANCEL", process_cancel },
+  { "BEGIN", process_begin },
+  { "DATA", process_data_server },
+  { "ERROR", process_error_server },
+  { NULL, NULL }
+};                  
+
+static DBusAuthCommandHandler
+client_handlers[] = {
+  { "MECHANISMS", process_mechanisms },
+  { "REJECTED", process_rejected },
+  { "OK", process_ok },
+  { "DATA", process_data_client },
+  { "ERROR", process_error_client },
+  { NULL, NULL }
+};
+
+/**
+ * @param auth the auth conversation
+ * @returns #TRUE if the conversation is the server side
+ */
+#define DBUS_AUTH_IS_SERVER(auth) ((auth)->handlers == server_handlers)
+/**
+ * @param auth the auth conversation
+ * @returns #TRUE if the conversation is the client side
+ */
+#define DBUS_AUTH_IS_CLIENT(auth) ((auth)->handlers == client_handlers)
+/**
+ * @param auth the auth conversation
+ * @returns auth cast to DBusAuthClient
+ */
+#define DBUS_AUTH_CLIENT(auth)    ((DBusAuthClient*)(auth))
+/**
+ * @param auth the auth conversation
+ * @returns auth cast to DBusAuthServer
+ */
+#define DBUS_AUTH_SERVER(auth)    ((DBusAuthServer*)(auth))
+
+static DBusAuth*
+_dbus_auth_new (int size)
+{
+  DBusAuth *auth;
+  
+  auth = dbus_malloc0 (size);
+  if (auth == NULL)
+    return NULL;
+  
+  auth->refcount = 1;
+
+  /* note that we don't use the max string length feature,
+   * because you can't use that feature if you're going to
+   * try to recover from out-of-memory (it creates
+   * what looks like unrecoverable inability to alloc
+   * more space in the string). But we do handle
+   * 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->outgoing, _DBUS_INT_MAX))
+    {
+      _dbus_string_free (&auth->outgoing);
+      dbus_free (auth);
+      return NULL;
+    }
+  
+  return auth;
+}
+
+static DBusAuthState
+get_state (DBusAuth *auth)
+{
+  if (auth->need_disconnect)
+    return DBUS_AUTH_STATE_NEED_DISCONNECT;
+  else if (auth->authenticated)
+    return DBUS_AUTH_STATE_AUTHENTICATED;
+  else if (auth->needed_memory)
+    return DBUS_AUTH_STATE_WAITING_FOR_MEMORY;
+  else if (_dbus_string_get_length (&auth->outgoing) > 0)
+    return DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
+  else
+    return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
+}
+
+static void
+shutdown_mech (DBusAuth *auth)
+{
+  /* Cancel any auth */
+  auth->authenticated_pending_begin = FALSE;
+  auth->authenticated = FALSE;
+  
+  if (auth->mech != NULL)
+    {      
+      if (DBUS_AUTH_IS_CLIENT (auth))
+        (* auth->mech->client_shutdown_func) (auth);
+      else
+        (* auth->mech->server_shutdown_func) (auth);
+      
+      auth->mech = NULL;
+    }
+}
+
+static dbus_bool_t
+handle_server_data_stupid_test_mech (DBusAuth         *auth,
+                                     const DBusString *data)
+{
+  if (!_dbus_string_append (&auth->outgoing,
+                            "OK\r\n"))
+    return FALSE;
+
+  auth->authenticated_pending_begin = TRUE;
+  
+  return TRUE;
+}
+
+static void
+handle_server_shutdown_stupid_test_mech (DBusAuth *auth)
+{
+
+}
+
+static dbus_bool_t
+handle_client_data_stupid_test_mech (DBusAuth         *auth,
+                                     const DBusString *data)
+{
+  
+  return TRUE;
+}
+
+static void
+handle_client_shutdown_stupid_test_mech (DBusAuth *auth)
+{
+
+}
+
+/* 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)
+{
+  if (!_dbus_string_base64_encode (plaintext, 0, encoded, 0))
+    return FALSE;
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+handle_decode_stupid_test_mech (DBusAuth         *auth,
+                                const DBusString *encoded,
+                                DBusString       *plaintext)
+{
+  if (!_dbus_string_base64_decode (encoded, 0, plaintext, 0))
+    return FALSE;
+  
+  return TRUE;
+}
+
+/* Put mechanisms here in order of preference.
+ * What I eventually want to have is:
+ *
+ *  - a mechanism that checks UNIX domain socket credentials
+ *  - a simple magic cookie mechanism like X11 or ICE
+ *  - mechanisms that chain to Cyrus SASL, so we can use anything it
+ *    offers such as Kerberos, X509, whatever.
+ * 
+ */
+static const DBusAuthMechanismHandler
+all_mechanisms[] = {
+  { "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,
+    handle_client_data_stupid_test_mech,
+    handle_encode_stupid_test_mech,
+    handle_decode_stupid_test_mech,
+    handle_client_shutdown_stupid_test_mech },
+  { NULL, NULL }
+};
+
+static const DBusAuthMechanismHandler*
+find_mech (const DBusString *name)
+{
+  int i;
+  
+  i = 0;
+  while (all_mechanisms[i].mechanism != NULL)
+    {
+      if (_dbus_string_equal_c_str (name,
+                                    all_mechanisms[i].mechanism))
+
+        return &all_mechanisms[i];
+      
+      ++i;
+    }
+  
+  return NULL;
+}
+
+static dbus_bool_t
+send_mechanisms (DBusAuth *auth)
+{
+  DBusString command;
+  int i;
+  
+  if (!_dbus_string_init (&command, _DBUS_INT_MAX))
+    return FALSE;
+  
+  if (!_dbus_string_append (&command,
+                            "MECHANISMS"))
+    goto nomem;
+
+  i = 0;
+  while (all_mechanisms[i].mechanism != NULL)
+    {
+      if (!_dbus_string_append (&command,
+                                " "))
+        goto nomem;
+
+      if (!_dbus_string_append (&command,
+                                all_mechanisms[i].mechanism))
+        goto nomem;
+      
+      ++i;
+    }
+  
+  if (!_dbus_string_append (&command, "\r\n"))
+    goto nomem;
+
+  if (!_dbus_string_copy (&command, 0, &auth->outgoing,
+                          _dbus_string_get_length (&auth->outgoing)))
+    goto nomem;
+  
+  return TRUE;
+
+ nomem:
+  _dbus_string_free (&command);
+  return FALSE;
+}
+
+static dbus_bool_t
+process_auth (DBusAuth         *auth,
+              const DBusString *command,
+              const DBusString *args)
+{
+  if (auth->mech)
+    {
+      /* We are already using a mechanism, client is on crack */
+      if (!_dbus_string_append (&auth->outgoing,
+                                "ERROR \"Sent AUTH while another AUTH in progress\"\r\n"))
+        return FALSE;
+
+      return TRUE;
+    }
+  else if (_dbus_string_get_length (args) == 0)
+    {
+      /* No args to the auth, send mechanisms */
+      if (!send_mechanisms (auth))
+        return FALSE;
+
+      return TRUE;
+    }
+  else
+    {
+      int i;
+      DBusString mech;
+      DBusString base64_response;
+      DBusString decoded_response;
+      
+      _dbus_string_find_blank (args, 0, &i);
+
+      if (!_dbus_string_init (&mech, _DBUS_INT_MAX))
+        return FALSE;
+
+      if (!_dbus_string_init (&base64_response, _DBUS_INT_MAX))
+        {
+          _dbus_string_free (&mech);
+          return FALSE;
+        }
+
+      if (!_dbus_string_init (&decoded_response, _DBUS_INT_MAX))
+        {
+          _dbus_string_free (&mech);
+          _dbus_string_free (&base64_response);
+          return FALSE;
+        }
+      
+      if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
+        goto failed;
+
+      if (!_dbus_string_copy (args, i, &base64_response, 0))
+        goto failed;
+
+      if (!_dbus_string_base64_decode (&base64_response, 0,
+                                       &decoded_response, 0))
+        goto failed;
+
+      auth->mech = find_mech (&mech);
+      if (auth->mech != NULL)
+        {
+          if (!(* auth->mech->server_data_func) (auth,
+                                                 &decoded_response))
+            goto failed;
+        }
+      else
+        {
+          /* Unsupported mechanism */
+          if (!send_mechanisms (auth))
+            return FALSE;
+        }
+      
+      return TRUE;
+      
+    failed:
+      auth->mech = NULL;
+      _dbus_string_free (&mech);
+      _dbus_string_free (&base64_response);
+      _dbus_string_free (&decoded_response);
+      return FALSE;
+    }
+}
+
+static dbus_bool_t
+process_cancel (DBusAuth         *auth,
+                const DBusString *command,
+                const DBusString *args)
+{
+  shutdown_mech (auth);
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+process_begin (DBusAuth         *auth,
+               const DBusString *command,
+               const DBusString *args)
+{
+  if (auth->authenticated_pending_begin)
+    auth->authenticated = TRUE;
+  else
+    {
+      auth->need_disconnect = TRUE; /* client trying to send data before auth,
+                                     * kick it
+                                     */
+      shutdown_mech (auth);
+    }
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+process_data_server (DBusAuth         *auth,
+                     const DBusString *command,
+                     const DBusString *args)
+{
+  if (auth->mech != NULL)
+    {
+      DBusString decoded;
+
+      if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
+        return FALSE;
+
+      if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
+        {
+          _dbus_string_free (&decoded);
+          return FALSE;
+        }
+      
+      if (!(* auth->mech->server_data_func) (auth, &decoded))
+        {
+          _dbus_string_free (&decoded);
+          return FALSE;
+        }
+
+      _dbus_string_free (&decoded);
+    }
+  else
+    {
+      if (!_dbus_string_append (&auth->outgoing,
+                                "ERROR \"Not currently in an auth conversation\"\r\n"))
+        return FALSE;
+    }
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+process_error_server (DBusAuth         *auth,
+                      const DBusString *command,
+                      const DBusString *args)
+{
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+get_word (const DBusString *str,
+          int              *start,
+          DBusString       *word)
+{
+  int i;
+
+  _dbus_string_find_blank (str, *start, &i);
+
+  if (i != *start)
+    {
+      if (!_dbus_string_copy_len (str, *start, i, word, 0))
+        return FALSE;
+      
+      *start = i;
+    }
+
+  return TRUE;
+}
+
+static dbus_bool_t
+process_mechanisms (DBusAuth         *auth,
+                    const DBusString *command,
+                    const DBusString *args)
+{
+  int next;
+  int len;
+
+  if (auth->already_got_mechanisms)
+    return TRUE;
+  
+  len = _dbus_string_get_length (args);
+  
+  next = 0;
+  while (next < len)
+    {
+      DBusString m;
+      const DBusAuthMechanismHandler *mech;
+
+      if (!_dbus_string_init (&m, _DBUS_INT_MAX))
+        goto nomem;
+      
+      if (!get_word (args, &next, &m))
+        goto nomem;
+
+      mech = find_mech (&m);
+
+      if (mech != NULL)
+        {
+          /* FIXME right now we try mechanisms in the order
+           * the server lists them; should we do them in
+           * some more deterministic order?
+           *
+           * Probably in all_mechanisms order, our order of
+           * preference. Of course when the server is us,
+           * it lists things in that order anyhow.
+           */
+          
+          if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
+                                  (void*) mech))
+            goto nomem;
+        }
+    }
+  
+  auth->already_got_mechanisms = TRUE;
+  
+  return TRUE;
+
+ nomem:
+  _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
+  
+  return FALSE;
+}
+
+static dbus_bool_t
+process_rejected (DBusAuth         *auth,
+                  const DBusString *command,
+                  const DBusString *args)
+{
+  shutdown_mech (auth);
+  
+  if (!auth->already_got_mechanisms)
+    {
+      /* Ask for mechanisms */
+      if (!_dbus_string_append (&auth->outgoing,
+                                "AUTH\r\n"))
+        return FALSE;
+    }
+  else if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL)
+    {
+      /* Try next mechanism */
+      const DBusAuthMechanismHandler *mech;
+      DBusString auth_command;
+
+      mech = DBUS_AUTH_CLIENT (auth)->mechs_to_try->data;
+
+      if (!_dbus_string_init (&auth_command, _DBUS_INT_MAX))
+        return FALSE;
+      
+      if (!_dbus_string_append (&auth_command,
+                                "AUTH "))
+        {
+          _dbus_string_free (&auth_command);
+          return FALSE;
+        }
+
+      if (!_dbus_string_append (&auth->outgoing,
+                                mech->mechanism))
+        {
+          _dbus_string_free (&auth_command);
+          return FALSE;
+        }
+        
+      if (!_dbus_string_append (&auth->outgoing,
+                                "\r\n"))
+        {
+          _dbus_string_free (&auth_command);
+          return FALSE;
+        }
+
+      if (!_dbus_string_copy (&auth_command, 0,
+                              &auth->outgoing,
+                              _dbus_string_get_length (&auth->outgoing)))
+        {
+          _dbus_string_free (&auth_command);
+          return FALSE;
+        }
+
+      _dbus_list_pop_first (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
+    }
+  else
+    {
+      /* Give up */
+      auth->need_disconnect = TRUE;
+    }
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+process_ok (DBusAuth         *auth,
+            const DBusString *command,
+            const DBusString *args)
+{
+  if (!_dbus_string_append (&auth->outgoing,
+                            "BEGIN\r\n"))
+    return FALSE;
+  
+  auth->authenticated_pending_output = TRUE;
+  
+  return TRUE;
+}
+
+
+static dbus_bool_t
+process_data_client (DBusAuth         *auth,
+                     const DBusString *command,
+                     const DBusString *args)
+{
+  if (auth->mech != NULL)
+    {
+      DBusString decoded;
+
+      if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
+        return FALSE;
+
+      if (!_dbus_string_base64_decode (args, 0, &decoded, 0))
+        {
+          _dbus_string_free (&decoded);
+          return FALSE;
+        }
+      
+      if (!(* auth->mech->client_data_func) (auth, &decoded))
+        {
+          _dbus_string_free (&decoded);
+          return FALSE;
+        }
+
+      _dbus_string_free (&decoded);
+    }
+  else
+    {
+      if (!_dbus_string_append (&auth->outgoing,
+                                "ERROR \"Got DATA when not in an auth exchange\"\r\n"))
+        return FALSE;
+    }
+  
+  return TRUE;
+}
+
+static dbus_bool_t
+process_error_client (DBusAuth         *auth,
+                      const DBusString *command,
+                      const DBusString *args)
+{
+  return TRUE;
+}
+
+static dbus_bool_t
+process_unknown (DBusAuth         *auth,
+                 const DBusString *command,
+                 const DBusString *args)
+{
+  if (!_dbus_string_append (&auth->outgoing,
+                            "ERROR \"Unknown command\"\r\n"))
+    return FALSE;
+
+  return TRUE;
+}
+
+/* returns whether to call it again right away */
+static dbus_bool_t
+process_command (DBusAuth *auth)
+{
+  DBusString command;
+  DBusString args;
+  int eol;
+  int i, j;
+  dbus_bool_t retval;
+
+  retval = FALSE;
+  
+  eol = 0;
+  if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
+    return FALSE;
+  
+  if (!_dbus_string_init (&command, _DBUS_INT_MAX))
+    {
+      auth->needed_memory = TRUE;
+      return FALSE;
+    }
+
+  if (!_dbus_string_init (&args, _DBUS_INT_MAX))
+    {
+      auth->needed_memory = TRUE;
+      return FALSE;
+    }
+
+  if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &command, 0))
+    goto out;
+  
+  _dbus_string_find_blank (&command, 0, &i);
+  _dbus_string_skip_blank (&command, i, &j);
+
+  if (i != j)
+    _dbus_string_delete (&command, i, j);
+  
+  if (!_dbus_string_move (&command, i, &args, 0))
+    goto out;
+
+  i = 0;
+  while (auth->handlers[i].command != NULL)
+    {
+      if (_dbus_string_equal_c_str (&command,
+                                    auth->handlers[i].command))
+        {
+          if (!(* auth->handlers[i].func) (auth, &command, &args))
+            goto out;
+
+          break;
+        }
+      ++i;
+    }
+
+  if (auth->handlers[i].command == NULL)
+    {
+      if (!process_unknown (auth, &command, &args))
+        goto out;
+    }
+  
+  /* We've succeeded in processing the whole command so drop it out
+   * of the incoming buffer and return TRUE to try another command.
+   */
+
+  _dbus_string_delete (&auth->incoming, 0, eol);
+  
+  /* kill the \r\n */
+  _dbus_string_delete (&auth->incoming, 0, 2);
+
+  retval = TRUE;
+  
+ out:
+  _dbus_string_free (&args);
+  _dbus_string_free (&command);
+
+  if (!retval)
+    auth->needed_memory = TRUE;
+  else
+    auth->needed_memory = FALSE;
+  
+  return retval;
+}
+
+
+/** @} */
+
+/**
+ * @addtogroup DBusAuth
+ * @{
+ */
+
+/**
+ * Creates a new auth conversation object for the server side.
+ * See doc/dbus-sasl-profile.txt for full details on what
+ * this object does.
+ *
+ * @returns the new object or #NULL if no memory
+ */
+DBusAuth*
+_dbus_auth_server_new (void)
+{
+  DBusAuth *auth;
+
+  auth = _dbus_auth_new (sizeof (DBusAuthServer));
+  if (auth == NULL)
+    return NULL;
+
+  auth->handlers = server_handlers;
+  
+  return auth;
+}
+
+/**
+ * Creates a new auth conversation object for the client side.
+ * See doc/dbus-sasl-profile.txt for full details on what
+ * this object does.
+ *
+ * @returns the new object or #NULL if no memory
+ */
+DBusAuth*
+_dbus_auth_client_new (void)
+{
+  DBusAuth *auth;
+
+  auth = _dbus_auth_new (sizeof (DBusAuthClient));
+  if (auth == NULL)
+    return NULL;
+
+  auth->handlers = client_handlers;
+
+  /* Request an auth */
+  if (!_dbus_string_append (&auth->outgoing,
+                            "AUTH DBUS_STUPID_TEST_MECH\r\n"))
+    {
+      _dbus_auth_unref (auth);
+      return NULL;
+    }
+  
+  return auth;
+}
+
+/**
+ * Increments the refcount of an auth object.
+ *
+ * @param auth the auth conversation
+ */
+void
+_dbus_auth_ref (DBusAuth *auth)
+{
+  _dbus_assert (auth != NULL);
+  
+  auth->refcount += 1;
+}
+
+/**
+ * Decrements the refcount of an auth object.
+ *
+ * @param auth the auth conversation
+ */
+void
+_dbus_auth_unref (DBusAuth *auth)
+{
+  _dbus_assert (auth != NULL);
+  _dbus_assert (auth->refcount > 0);
+
+  auth->refcount -= 1;
+  if (auth->refcount == 0)
+    {
+      shutdown_mech (auth);
+
+      if (DBUS_AUTH_IS_CLIENT (auth))
+        {
+          _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
+        }
+      
+      _dbus_string_free (&auth->incoming);
+      _dbus_string_free (&auth->outgoing);
+      dbus_free (auth);
+    }
+}
+
+/**
+ * @param auth the auth conversation object
+ * @returns #TRUE if we're in a final state
+ */
+#define DBUS_AUTH_IN_END_STATE(auth) ((auth)->need_disconnect || (auth)->authenticated)
+
+/**
+ * Analyzes buffered input and moves the auth conversation forward,
+ * returning the new state of the auth conversation.
+ *
+ * @param auth the auth conversation
+ * @returns the new state
+ */
+DBusAuthState
+_dbus_auth_do_work (DBusAuth *auth)
+{  
+  if (DBUS_AUTH_IN_END_STATE (auth))
+    return get_state (auth);
+
+  auth->needed_memory = FALSE;
+
+  /* Max amount we'll buffer up before deciding someone's on crack */
+#define MAX_BUFFER (16 * 1024)
+
+  do
+    {
+      if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER ||
+          _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER)
+        {
+          auth->need_disconnect = TRUE;
+          _dbus_verbose ("Disconnecting due to excessive data buffered in auth phase\n");
+          break;
+        }
+    }
+  while (process_command (auth));
+  
+  return get_state (auth);
+}
+
+/**
+ * Gets bytes that need to be sent to the
+ * peer we're conversing with.
+ *
+ * @param auth the auth conversation
+ * @param str initialized string object to be filled in with bytes to send
+ * @returns #FALSE if not enough memory to fill in the bytes
+ */
+dbus_bool_t
+_dbus_auth_get_bytes_to_send (DBusAuth   *auth,
+                              DBusString *str)
+{
+  _dbus_assert (auth != NULL);
+  _dbus_assert (str != NULL);
+  
+  if (DBUS_AUTH_IN_END_STATE (auth))
+    return FALSE;
+
+  auth->needed_memory = FALSE;
+  
+  _dbus_string_set_length (str, 0);
+
+  if (!_dbus_string_move (&auth->outgoing,
+                          0, str, 0))
+    {
+      auth->needed_memory = TRUE;
+      return FALSE;
+    }
+
+  if (auth->authenticated_pending_output)
+    auth->authenticated = TRUE;
+  
+  return TRUE;
+}
+
+/**
+ * Stores bytes received from the peer we're conversing with.
+ *
+ * @param auth the auth conversation
+ * @param str the received bytes.
+ * @returns #FALSE if not enough memory to store the bytes.
+ */
+dbus_bool_t
+_dbus_auth_bytes_received (DBusAuth   *auth,
+                           const DBusString *str)
+{
+  _dbus_assert (auth != NULL);
+  _dbus_assert (str != NULL);
+  
+  if (DBUS_AUTH_IN_END_STATE (auth))
+    return FALSE;
+
+  auth->needed_memory = FALSE;
+  
+  if (!_dbus_string_copy (str, 0,
+                          &auth->incoming,
+                          _dbus_string_get_length (&auth->incoming)))
+    {
+      auth->needed_memory = TRUE;
+      return FALSE;
+    }
+
+  _dbus_auth_do_work (auth);
+  
+  return TRUE;
+}
+
+/**
+ * Returns leftover bytes that were not used as part of the auth
+ * conversation.  These bytes will be part of the message stream
+ * instead. This function may not be called until authentication has
+ * succeeded.
+ *
+ * @param auth the auth conversation
+ * @param str string to place the unused bytes in
+ * @returns #FALSE if not enough memory to return the bytes
+ */
+dbus_bool_t
+_dbus_auth_get_unused_bytes (DBusAuth   *auth,
+                             DBusString *str)
+{
+  if (!DBUS_AUTH_IN_END_STATE (auth))
+    return FALSE;
+  
+  if (!_dbus_string_move (&auth->incoming,
+                          0, str, 0))
+    return FALSE;
+
+  return TRUE;
+}
+
+/**
+ * Called post-authentication, indicates whether we need to encode
+ * the message stream with _dbus_auth_encode_data() prior to
+ * sending it to the peer.
+ *
+ * @param auth the auth conversation
+ * @returns #TRUE if we need to encode the stream
+ */
+dbus_bool_t
+_dbus_auth_needs_encoding (DBusAuth *auth)
+{
+  if (!auth->authenticated)
+    return FALSE;
+  
+  if (auth->mech != NULL)
+    {
+      if (DBUS_AUTH_IS_CLIENT (auth))
+        return auth->mech->client_encode_func != NULL;
+      else
+        return auth->mech->server_encode_func != NULL;
+    }
+  else
+    return FALSE;
+}
+
+/**
+ * Called post-authentication, encodes a block of bytes for sending to
+ * the peer. If no encoding was negotiated, just copies the bytes
+ * (you can avoid this by checking _dbus_auth_needs_encoding()).
+ *
+ * @param auth the auth conversation
+ * @param plaintext the plain text data
+ * @param encoded initialized string to fill in with encoded data
+ * @returns #TRUE if we had enough memory and successfully encoded
+ */
+dbus_bool_t
+_dbus_auth_encode_data (DBusAuth         *auth,
+                        const DBusString *plaintext,
+                        DBusString       *encoded)
+{
+  if (!auth->authenticated)
+    return FALSE;
+  
+  if (_dbus_auth_needs_encoding (auth))
+    {
+      if (DBUS_AUTH_IS_CLIENT (auth))
+        return (* auth->mech->client_encode_func) (auth, plaintext, encoded);
+      else
+        return (* auth->mech->server_encode_func) (auth, plaintext, encoded);
+    }
+  else
+    {
+      return _dbus_string_copy (plaintext, 0, encoded, 0);
+    }
+}
+
+/**
+ * Called post-authentication, indicates whether we need to decode
+ * the message stream with _dbus_auth_decode_data() after
+ * receiving it from the peer.
+ *
+ * @param auth the auth conversation
+ * @returns #TRUE if we need to encode the stream
+ */
+dbus_bool_t
+_dbus_auth_needs_decoding (DBusAuth *auth)
+{
+  if (!auth->authenticated)
+    return FALSE;
+    
+  if (auth->mech != NULL)
+    {
+      if (DBUS_AUTH_IS_CLIENT (auth))
+        return auth->mech->client_decode_func != NULL;
+      else
+        return auth->mech->server_decode_func != NULL;
+    }
+  else
+    return FALSE;
+}
+
+
+/**
+ * Called post-authentication, decodes a block of bytes received from
+ * the peer. If no encoding was negotiated, just copies the bytes (you
+ * can avoid this by checking _dbus_auth_needs_decoding()).
+ *
+ * @param auth the auth conversation
+ * @param encoded the encoded data
+ * @param plaintext initialized string to fill in with decoded data
+ * @returns #TRUE if we had enough memory and successfully decoded
+ */
+dbus_bool_t
+_dbus_auth_decode_data (DBusAuth         *auth,
+                        const DBusString *encoded,
+                        DBusString       *plaintext)
+{
+  if (!auth->authenticated)
+    return FALSE;
+  
+  if (_dbus_auth_needs_decoding (auth))
+    {
+      if (DBUS_AUTH_IS_CLIENT (auth))
+        return (* auth->mech->client_decode_func) (auth, encoded, plaintext);
+      else
+        return (* auth->mech->server_decode_func) (auth, encoded, plaintext);
+    }
+  else
+    {
+      return _dbus_string_copy (encoded, 0, plaintext, 0);
+    }
+}
+
+/** @} */
diff --git a/dbus/dbus-auth.h b/dbus/dbus-auth.h
new file mode 100644 (file)
index 0000000..1d0baa9
--- /dev/null
@@ -0,0 +1,66 @@
+/* -*- mode: C; c-file-style: "gnu" -*- */
+/* dbus-auth.h Authentication
+ *
+ * Copyright (C) 2002  Red Hat Inc.
+ *
+ * Licensed under the Academic Free License version 1.2
+ * 
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ */
+#ifndef DBUS_AUTH_H
+#define DBUS_AUTH_H
+
+#include <dbus/dbus-macros.h>
+#include <dbus/dbus-errors.h>
+#include <dbus/dbus-string.h>
+
+DBUS_BEGIN_DECLS;
+
+typedef struct DBusAuth DBusAuth;
+
+typedef enum
+{
+  DBUS_AUTH_STATE_WAITING_FOR_INPUT,
+  DBUS_AUTH_STATE_WAITING_FOR_MEMORY,
+  DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND,
+  DBUS_AUTH_STATE_NEED_DISCONNECT,
+  DBUS_AUTH_STATE_AUTHENTICATED
+} DBusAuthState;
+
+DBusAuth*     _dbus_auth_server_new        (void);
+DBusAuth*     _dbus_auth_client_new        (void);
+void          _dbus_auth_ref               (DBusAuth         *auth);
+void          _dbus_auth_unref             (DBusAuth         *auth);
+DBusAuthState _dbus_auth_do_work           (DBusAuth         *auth);
+dbus_bool_t   _dbus_auth_get_bytes_to_send (DBusAuth         *auth,
+                                            DBusString       *str);
+dbus_bool_t   _dbus_auth_bytes_received    (DBusAuth         *auth,
+                                            const DBusString *str);
+dbus_bool_t   _dbus_auth_get_unused_bytes  (DBusAuth         *auth,
+                                            DBusString       *str);
+dbus_bool_t   _dbus_auth_needs_encoding    (DBusAuth         *auth);
+dbus_bool_t   _dbus_auth_encode_data       (DBusAuth         *auth,
+                                            const DBusString *plaintext,
+                                            DBusString       *encoded);
+dbus_bool_t   _dbus_auth_needs_decoding    (DBusAuth         *auth);
+dbus_bool_t   _dbus_auth_decode_data       (DBusAuth         *auth,
+                                            const DBusString *encoded,
+                                            DBusString       *plaintext);
+
+
+DBUS_END_DECLS;
+
+#endif /* DBUS_AUTH_H */
index 2b8d134..ef015c6 100644 (file)
@@ -711,11 +711,9 @@ _dbus_string_delete (DBusString       *str,
 }
 
 static dbus_bool_t
-copy (DBusRealString *source,
-      int             start,
-      int             len,
-      DBusRealString *dest,
-      int             insert_at)
+open_gap (int             len,
+          DBusRealString *dest,
+          int             insert_at)
 {
   if (len == 0)
     return TRUE;
@@ -727,6 +725,19 @@ copy (DBusRealString *source,
            dest->str + insert_at,
            dest->len - len);
 
+  return TRUE;
+}
+
+static dbus_bool_t
+copy (DBusRealString *source,
+      int             start,
+      int             len,
+      DBusRealString *dest,
+      int             insert_at)
+{
+  if (!open_gap (len, dest, insert_at))
+    return FALSE;
+    
   memcpy (dest->str + insert_at,
           source->str + start,
           len);
@@ -1003,6 +1014,579 @@ _dbus_string_get_unichar (const DBusString *str,
     *end_return = start + len;
 }
 
+/**
+ * Finds the given substring in the string,
+ * returning #TRUE and filling in the byte index
+ * where the substring was found, if it was found.
+ * Returns #FALSE if the substring wasn't found.
+ * Sets *start to the length of the string if the substring
+ * is not found.
+ *
+ * @param str the string
+ * @param start where to start looking
+ * @param substr the substring
+ * @param found return location for where it was found, or #NULL
+ * @returns #TRUE if found
+ */
+dbus_bool_t
+_dbus_string_find (const DBusString *str,
+                   int               start,
+                   const char       *substr,
+                   int              *found)
+{
+  int i;
+  DBUS_CONST_STRING_PREAMBLE (str);
+  _dbus_assert (substr != NULL);
+  _dbus_assert (start <= real->len);
+  
+  /* we always "find" an empty string */
+  if (*substr == '\0')
+    {
+      if (found)
+        *found = 0;
+      return TRUE;
+    }
+  
+  i = start;
+  while (i < real->len)
+    {
+      if (real->str[i] == substr[0])
+        {
+          int j = i + 1;
+          
+          while (j < real->len)
+            {
+              if (substr[j - i] == '\0')
+                break;
+              else if (real->str[j] != substr[j - i])
+                break;
+              
+              ++j;
+            }
+
+          if (substr[j - i] == '\0')
+            {
+              if (found)
+                *found = i;
+              return TRUE;
+            }
+        }
+      
+      ++i;
+    }
+
+  if (found)
+    *found = real->len;
+  
+  return FALSE;
+}
+
+/**
+ * Finds a blank (space or tab) in the string. Returns #TRUE
+ * if found, #FALSE otherwise. If a blank is not found sets
+ * *found to the length of the string.
+ *
+ * @param str the string
+ * @param start byte index to start looking
+ * @param found place to store the location of the first blank
+ * @returns #TRUE if a blank was found
+ */
+dbus_bool_t
+_dbus_string_find_blank (const DBusString *str,
+                         int               start,
+                         int              *found)
+{
+  int i;
+  DBUS_CONST_STRING_PREAMBLE (str);
+  _dbus_assert (start <= real->len);
+  
+  i = start;
+  while (i < real->len)
+    {
+      if (real->str[i] == ' ' ||
+          real->str[i] == '\t')
+        {
+          if (found)
+            *found = i;
+          return TRUE;
+        }
+      
+      ++i;
+    }
+
+  if (found)
+    *found = real->len;
+  
+  return FALSE;
+}
+
+/**
+ * Skips blanks from start, storing the first non-blank in *end
+ *
+ * @param str the string
+ * @param start where to start
+ * @param end where to store the first non-blank byte index
+ */
+void
+_dbus_string_skip_blank (const DBusString *str,
+                         int               start,
+                         int              *end)
+{
+  int i;
+  DBUS_CONST_STRING_PREAMBLE (str);
+  _dbus_assert (start <= real->len);
+  
+  i = start;
+  while (i < real->len)
+    {
+      if (real->str[i] != ' ' ||
+          real->str[i] != '\t')
+        break;
+      
+      ++i;
+    }
+
+  if (end)
+    *end = i;
+}
+
+/**
+ * Tests two DBusString for equality.
+ *
+ * @param a first string
+ * @param b second string
+ * @returns #TRUE if equal
+ */
+dbus_bool_t
+_dbus_string_equal (const DBusString *a,
+                    const DBusString *b)
+{
+  const unsigned char *ap;
+  const unsigned char *bp;
+  const unsigned char *a_end;
+  const DBusRealString *real_a = (const DBusRealString*) a;
+  const DBusRealString *real_b = (const DBusRealString*) b;
+  DBUS_GENERIC_STRING_PREAMBLE (real_a);
+  DBUS_GENERIC_STRING_PREAMBLE (real_b);
+
+  if (real_a->len != real_b->len)
+    return FALSE;
+
+  ap = real_a->str;
+  bp = real_b->str;
+  a_end = real_a->str + real_a->len;
+  while (ap != a_end)
+    {
+      if (*ap != *bp)
+        return FALSE;
+      
+      ++ap;
+      ++bp;
+    }
+
+  return TRUE;
+}
+
+/**
+ * Checks whether a string is equal to a C string.
+ *
+ * @param a the string
+ * @param c_str the C string
+ * @returns #TRUE if equal
+ */
+dbus_bool_t
+_dbus_string_equal_c_str (const DBusString *a,
+                          const char       *c_str)
+{
+  const unsigned char *ap;
+  const unsigned char *bp;
+  const unsigned char *a_end;
+  const DBusRealString *real_a = (const DBusRealString*) a;
+  DBUS_GENERIC_STRING_PREAMBLE (real_a);
+
+  ap = real_a->str;
+  bp = (const unsigned char*) c_str;
+  a_end = real_a->str + real_a->len;
+  while (ap != a_end && *bp)
+    {
+      if (*ap != *bp)
+        return FALSE;
+      
+      ++ap;
+      ++bp;
+    }
+
+  if (*ap && *bp == '\0')
+    return FALSE;
+  else if (ap == a_end && *bp)
+    return FALSE;
+  
+  return TRUE;
+}
+
+static const signed char base64_table[] = {
+  /* 0 */ 'A',
+  /* 1 */ 'B',
+  /* 2 */ 'C',
+  /* 3 */ 'D',
+  /* 4 */ 'E',
+  /* 5 */ 'F',
+  /* 6 */ 'G',
+  /* 7 */ 'H',
+  /* 8 */ 'I',
+  /* 9 */ 'J',
+  /* 10 */ 'K',
+  /* 11 */ 'L',
+  /* 12 */ 'M',
+  /* 13 */ 'N',
+  /* 14 */ 'O',
+  /* 15 */ 'P',
+  /* 16 */ 'Q',
+  /* 17 */ 'R',
+  /* 18 */ 'S',
+  /* 19 */ 'T',
+  /* 20 */ 'U',
+  /* 21 */ 'V',
+  /* 22 */ 'W',
+  /* 23 */ 'X',
+  /* 24 */ 'Y',
+  /* 25 */ 'Z',
+  /* 26 */ 'a',
+  /* 27 */ 'b',
+  /* 28 */ 'c',
+  /* 29 */ 'd',
+  /* 30 */ 'e',
+  /* 31 */ 'f',
+  /* 32 */ 'g',
+  /* 33 */ 'h',
+  /* 34 */ 'i',
+  /* 35 */ 'j',
+  /* 36 */ 'k',
+  /* 37 */ 'l',
+  /* 38 */ 'm',
+  /* 39 */ 'n',
+  /* 40 */ 'o',
+  /* 41 */ 'p',
+  /* 42 */ 'q',
+  /* 43 */ 'r',
+  /* 44 */ 's',
+  /* 45 */ 't',
+  /* 46 */ 'u',
+  /* 47 */ 'v',
+  /* 48 */ 'w',
+  /* 49 */ 'x',
+  /* 50 */ 'y',
+  /* 51 */ 'z',
+  /* 52 */ '0',
+  /* 53 */ '1',
+  /* 54 */ '2',
+  /* 55 */ '3',
+  /* 56 */ '4',
+  /* 57 */ '5',
+  /* 58 */ '6',
+  /* 59 */ '7',
+  /* 60 */ '8',
+  /* 61 */ '9',
+  /* 62 */ '+',
+  /* 63 */ '/'
+};
+
+/** The minimum char that's a valid char in Base64-encoded text */
+#define UNBASE64_MIN_CHAR (43)
+/** The maximum char that's a valid char in Base64-encoded text */
+#define UNBASE64_MAX_CHAR (122)
+/** Must subtract this from a char's integer value before offsetting
+ * into unbase64_table
+ */
+#define UNBASE64_TABLE_OFFSET UNBASE64_MIN_CHAR
+static const signed char unbase64_table[] = {
+  /* 43 + */ 62,
+  /* 44 , */ -1,
+  /* 45 - */ -1,
+  /* 46 . */ -1,
+  /* 47 / */ 63,
+  /* 48 0 */ 52,
+  /* 49 1 */ 53,
+  /* 50 2 */ 54,
+  /* 51 3 */ 55,
+  /* 52 4 */ 56,
+  /* 53 5 */ 57,
+  /* 54 6 */ 58,
+  /* 55 7 */ 59,
+  /* 56 8 */ 60,
+  /* 57 9 */ 61,
+  /* 58 : */ -1,
+  /* 59 ; */ -1,
+  /* 60 < */ -1,
+  /* 61 = */ -1,
+  /* 62 > */ -1,
+  /* 63 ? */ -1,
+  /* 64 @ */ -1,
+  /* 65 A */ 0,
+  /* 66 B */ 1,
+  /* 67 C */ 2,
+  /* 68 D */ 3,
+  /* 69 E */ 4,
+  /* 70 F */ 5,
+  /* 71 G */ 6,
+  /* 72 H */ 7,
+  /* 73 I */ 8,
+  /* 74 J */ 9,
+  /* 75 K */ 10,
+  /* 76 L */ 11,
+  /* 77 M */ 12,
+  /* 78 N */ 13,
+  /* 79 O */ 14,
+  /* 80 P */ 15,
+  /* 81 Q */ 16,
+  /* 82 R */ 17,
+  /* 83 S */ 18,
+  /* 84 T */ 19,
+  /* 85 U */ 20,
+  /* 86 V */ 21,
+  /* 87 W */ 22,
+  /* 88 X */ 23,
+  /* 89 Y */ 24,
+  /* 90 Z */ 25,
+  /* 91 [ */ -1,
+  /* 92 \ */ -1,
+  /* 93 ] */ -1,
+  /* 94 ^ */ -1,
+  /* 95 _ */ -1,
+  /* 96 ` */ -1,
+  /* 97 a */ 26,
+  /* 98 b */ 27,
+  /* 99 c */ 28,
+  /* 100 d */ 29,
+  /* 101 e */ 30,
+  /* 102 f */ 31,
+  /* 103 g */ 32,
+  /* 104 h */ 33,
+  /* 105 i */ 34,
+  /* 106 j */ 35,
+  /* 107 k */ 36,
+  /* 108 l */ 37,
+  /* 109 m */ 38,
+  /* 110 n */ 39,
+  /* 111 o */ 40,
+  /* 112 p */ 41,
+  /* 113 q */ 42,
+  /* 114 r */ 43,
+  /* 115 s */ 44,
+  /* 116 t */ 45,
+  /* 117 u */ 46,
+  /* 118 v */ 47,
+  /* 119 w */ 48,
+  /* 120 x */ 49,
+  /* 121 y */ 50,
+  /* 122 z */ 51
+};
+
+/**
+ * Encodes a string using Base64, as documented in RFC 2045.
+ *
+ * @param source the string to encode
+ * @param start byte index to start encoding
+ * @param dest string where encoded data should be placed
+ * @param insert_at where to place encoded data
+ * @returns #TRUE if encoding was successful, #FALSE if no memory etc.
+ */
+dbus_bool_t
+_dbus_string_base64_encode (const DBusString *source,
+                            int               start,
+                            DBusString       *dest,
+                            int               insert_at)
+{
+  int source_len;
+  int dest_len;
+  const unsigned char *s;
+  unsigned char *d;
+  const unsigned char *triplet_end;
+  const unsigned char *final_end;
+  DBUS_STRING_COPY_PREAMBLE (source, start, dest, insert_at);  
+  
+  /* For each 24 bits (3 bytes) of input, we have 4 chars of
+   * output.
+   */
+  source_len = real_source->len - start;
+  dest_len = (source_len / 3) * 4;
+  if (source_len % 3 != 0)
+    dest_len += 4;
+
+  if (source_len == 0)
+    return TRUE;
+  
+  if (!open_gap (dest_len, real_dest, insert_at))
+    return FALSE;
+
+  d = real_dest->str + insert_at;
+  s = real_source->str + start;
+  final_end = real_source->str + (start + source_len);
+  triplet_end = final_end - (source_len % 3);
+  _dbus_assert (triplet_end <= final_end);
+  _dbus_assert ((final_end - triplet_end) < 3);
+
+#define ENCODE_64(v) (base64_table[ (unsigned char) (v) ])
+#define SIX_BITS_MASK (0x3f)
+  _dbus_assert (SIX_BITS_MASK < _DBUS_N_ELEMENTS (base64_table));
+  
+  while (s != triplet_end)
+    {
+      unsigned int triplet;
+
+      triplet = s[0] | (s[1] << 8) | (s[2] << 16);
+
+      /* Encode each 6 bits */
+      
+      *d++ = ENCODE_64 (triplet >> 18);
+      *d++ = ENCODE_64 ((triplet >> 12) & SIX_BITS_MASK);
+      *d++ = ENCODE_64 ((triplet >> 6) & SIX_BITS_MASK);
+      *d++ = ENCODE_64 (triplet & SIX_BITS_MASK);
+      
+      s += 3;
+    }
+
+  switch (final_end - triplet_end)
+    {
+    case 2:
+      {
+        unsigned int doublet;
+        
+        doublet = s[0] | (s[1] << 8);
+        
+        *d++ = ENCODE_64 (doublet >> 12);
+        *d++ = ENCODE_64 ((doublet >> 6) & SIX_BITS_MASK);
+        *d++ = ENCODE_64 (doublet & SIX_BITS_MASK);
+        *d++ = '=';
+      }
+      break;
+    case 1:
+      {
+        unsigned int singlet;
+        
+        singlet = s[0];
+        
+        *d++ = ENCODE_64 ((singlet >> 6) & SIX_BITS_MASK);
+        *d++ = ENCODE_64 (singlet & SIX_BITS_MASK);
+        *d++ = '=';
+        *d++ = '=';
+      }
+      break;
+    case 0:
+      break;
+    }
+
+  _dbus_assert (d == (real_dest->str + (insert_at + dest_len)));
+
+  return TRUE;
+}
+
+
+/**
+ * Decodes a string from Base64, as documented in RFC 2045.
+ *
+ * @param source the string to decode
+ * @param start byte index to start decode
+ * @param dest string where decoded data should be placed
+ * @param insert_at where to place decoded data
+ * @returns #TRUE if decoding was successful, #FALSE if no memory etc.
+ */
+dbus_bool_t
+_dbus_string_base64_decode (const DBusString *source,
+                            int               start,
+                            DBusString       *dest,
+                            int               insert_at)
+{
+  int source_len;
+  const char *s;
+  const char *end;
+  DBusString result;
+  unsigned int triplet = 0;
+  int sextet_count;
+  int pad_count;
+  DBUS_STRING_COPY_PREAMBLE (source, start, dest, insert_at);
+  
+  source_len = real_source->len - start;
+  s = real_source->str + start;
+  end = real_source->str + source_len;
+
+  if (source_len == 0)
+    return TRUE;
+
+  if (!_dbus_string_init (&result, _DBUS_INT_MAX))
+    return FALSE;
+
+  pad_count = 0;
+  sextet_count = 0;
+  while (s != end)
+    {
+      /* The idea is to just skip anything that isn't
+       * a base64 char - it's allowed to have whitespace,
+       * newlines, etc. in here. We also ignore trailing
+       * base64 chars, though that's suspicious.
+       */
+      
+      if (*s >= UNBASE64_MIN_CHAR &&
+          *s <= UNBASE64_MAX_CHAR)
+        {
+          if (*s == '=')
+            {
+              /* '=' is padding, doesn't represent additional data
+               * but does increment our count.
+               */
+              pad_count += 1;
+              sextet_count += 1;
+            }
+          else
+            {
+              int val;
+
+              val = unbase64_table[(*s) - UNBASE64_TABLE_OFFSET];
+
+              if (val >= 0)
+                {
+                  triplet <<= 6;
+                  triplet |= (unsigned int) val;
+                  sextet_count += 1;
+                }
+            }
+
+          if (sextet_count == 4)
+            {
+              /* no pad = 3 bytes, 1 pad = 2 bytes, 2 pad = 1 byte */
+              
+              _dbus_string_append_byte (&result,
+                                        triplet & 0xff);
+              
+              if (pad_count < 2)
+                _dbus_string_append_byte (&result,
+                                          (triplet >> 8) & 0xff);
+
+              if (pad_count < 1)
+                _dbus_string_append_byte (&result,
+                                          triplet >> 16);
+
+              sextet_count = 0;
+              pad_count = 0;
+              triplet = 0;
+            }
+        }
+      
+      ++s;
+    }
+
+  if (!_dbus_string_move (&result, 0, dest, insert_at))
+    {
+      _dbus_string_free (&result);
+      return FALSE;
+    }
+
+  _dbus_string_free (&result);
+
+  return TRUE;
+}
+
+
 /** @} */
 
 #ifdef DBUS_BUILD_TESTS
@@ -1029,6 +1613,54 @@ test_max_len (DBusString *str,
     _dbus_assert_not_reached ("setting len to zero should have worked");
 }
 
+static void
+test_base64_roundtrip (const unsigned char *data,
+                       int                  len)
+{
+  DBusString orig;
+  DBusString encoded;
+  DBusString decoded;
+
+  if (len < 0)
+    len = strlen (data);
+  
+  if (!_dbus_string_init (&orig, _DBUS_INT_MAX))
+    _dbus_assert_not_reached ("could not init string");
+
+  if (!_dbus_string_init (&encoded, _DBUS_INT_MAX))
+    _dbus_assert_not_reached ("could not init string");
+  
+  if (!_dbus_string_init (&decoded, _DBUS_INT_MAX))
+    _dbus_assert_not_reached ("could not init string");
+
+  if (!_dbus_string_append_len (&orig, data, len))
+    _dbus_assert_not_reached ("couldn't append orig data");
+
+  if (!_dbus_string_base64_encode (&orig, 0, &encoded, 0))
+    _dbus_assert_not_reached ("could not encode");
+
+  if (!_dbus_string_base64_decode (&encoded, 0, &decoded, 0))
+    _dbus_assert_not_reached ("could not decode");
+
+  if (!_dbus_string_equal (&orig, &decoded))
+    {
+      const char *s;
+      
+      printf ("Original string %d bytes encoded %d bytes decoded %d bytes\n",
+              _dbus_string_get_length (&orig),
+              _dbus_string_get_length (&encoded),
+              _dbus_string_get_length (&decoded));
+      printf ("Original: %s\n", data);
+      _dbus_string_get_const_data (&decoded, &s);
+      printf ("Decoded: %s\n", s);
+      _dbus_assert_not_reached ("original string not the same as string decoded from base64");
+    }
+  
+  _dbus_string_free (&orig);
+  _dbus_string_free (&encoded);
+  _dbus_string_free (&decoded);  
+}
+
 /**
  * @ingroup DBusStringInternals
  * Unit test for DBusString.
@@ -1219,6 +1851,93 @@ _dbus_string_test (void)
 
   _dbus_string_free (&str);
 
+  /* Test find */
+  if (!_dbus_string_init (&str, _DBUS_INT_MAX))
+    _dbus_assert_not_reached ("failed to init string");
+
+  if (!_dbus_string_append (&str, "Hello"))
+    _dbus_assert_not_reached ("couldn't append to string");
+  
+  if (!_dbus_string_find (&str, 0, "He", &i))
+    _dbus_assert_not_reached ("didn't find 'He'");
+  _dbus_assert (i == 0);
+
+  if (!_dbus_string_find (&str, 0, "ello", &i))
+    _dbus_assert_not_reached ("didn't find 'ello'");
+  _dbus_assert (i == 1);
+
+  if (!_dbus_string_find (&str, 0, "lo", &i))
+    _dbus_assert_not_reached ("didn't find 'lo'");
+  _dbus_assert (i == 3);
+
+  if (!_dbus_string_find (&str, 2, "lo", &i))
+    _dbus_assert_not_reached ("didn't find 'lo'");
+  _dbus_assert (i == 3);
+
+  if (_dbus_string_find (&str, 4, "lo", &i))
+    _dbus_assert_not_reached ("did find 'lo'");
+  
+  if (!_dbus_string_find (&str, 0, "l", &i))
+    _dbus_assert_not_reached ("didn't find 'l'");
+  _dbus_assert (i == 2);
+
+  if (!_dbus_string_find (&str, 0, "H", &i))
+    _dbus_assert_not_reached ("didn't find 'H'");
+  _dbus_assert (i == 0);
+
+  if (!_dbus_string_find (&str, 0, "", &i))
+    _dbus_assert_not_reached ("didn't find ''");
+  _dbus_assert (i == 0);
+  
+  if (_dbus_string_find (&str, 0, "Hello!", NULL))
+    _dbus_assert_not_reached ("Did find 'Hello!'");
+
+  if (_dbus_string_find (&str, 0, "Oh, Hello", NULL))
+    _dbus_assert_not_reached ("Did find 'Oh, Hello'");
+  
+  if (_dbus_string_find (&str, 0, "ill", NULL))
+    _dbus_assert_not_reached ("Did find 'ill'");
+
+  if (_dbus_string_find (&str, 0, "q", NULL))
+    _dbus_assert_not_reached ("Did find 'q'");
+  
+  _dbus_string_free (&str);
+
+  /* Base 64 */
+  test_base64_roundtrip ("Hello this is a string\n", -1);
+  test_base64_roundtrip ("Hello this is a string\n1", -1);
+  test_base64_roundtrip ("Hello this is a string\n12", -1);
+  test_base64_roundtrip ("Hello this is a string\n123", -1);
+  test_base64_roundtrip ("Hello this is a string\n1234", -1);
+  test_base64_roundtrip ("Hello this is a string\n12345", -1);
+  test_base64_roundtrip ("", 0);
+  test_base64_roundtrip ("1", 1);
+  test_base64_roundtrip ("12", 2);
+  test_base64_roundtrip ("123", 3);
+  test_base64_roundtrip ("1234", 4);
+  test_base64_roundtrip ("12345", 5);
+  test_base64_roundtrip ("", 1);
+  test_base64_roundtrip ("1", 2);
+  test_base64_roundtrip ("12", 3);
+  test_base64_roundtrip ("123", 4);
+  test_base64_roundtrip ("1234", 5);
+  test_base64_roundtrip ("12345", 6);
+  {
+    unsigned char buf[512];
+    i = 0;
+    while (i < _DBUS_N_ELEMENTS (buf))
+      {
+        buf[i] = i;
+        ++i;
+      }
+    i = 0;
+    while (i < _DBUS_N_ELEMENTS (buf))
+      {
+        test_base64_roundtrip (buf, i);
+        ++i;
+      }
+  }
+  
   return TRUE;
 }
 
index b2f9957..0d495ab 100644 (file)
@@ -131,6 +131,33 @@ dbus_bool_t _dbus_string_parse_double (const DBusString *str,
                                        double           *value,
                                        int              *end_return);
 
+dbus_bool_t _dbus_string_find         (const DBusString *str,
+                                       int               start,
+                                       const char       *substr,
+                                       int              *found);
+
+dbus_bool_t _dbus_string_find_blank   (const DBusString *str,
+                                       int               start,
+                                       int              *found);
+
+void        _dbus_string_skip_blank   (const DBusString *str,
+                                       int               start,
+                                       int              *end);
+
+dbus_bool_t _dbus_string_equal        (const DBusString *a,
+                                       const DBusString *b);
+
+dbus_bool_t _dbus_string_equal_c_str  (const DBusString *a,
+                                       const char       *c_str);
+
+dbus_bool_t _dbus_string_base64_encode (const DBusString *source,
+                                        int               start,
+                                        DBusString       *dest,
+                                        int               insert_at);
+dbus_bool_t _dbus_string_base64_decode (const DBusString *source,
+                                        int               start,
+                                        DBusString       *dest,
+                                        int               insert_at);
 
 DBUS_END_DECLS;
 
index d09c548..095675b 100644 (file)
@@ -37,6 +37,10 @@ int
 main (int    argc,
       char **argv)
 {
+  printf ("%s: running string tests\n", argv[0]);
+  if (!_dbus_string_test ())
+    die ("strings");
+  
   printf ("%s: running marshalling tests\n", argv[0]);
   if (!_dbus_marshal_test ())
     die ("marshalling");
@@ -45,10 +49,6 @@ main (int    argc,
   if (!_dbus_mem_pool_test ())
     die ("memory pools");
   
-  printf ("%s: running string tests\n", argv[0]);
-  if (!_dbus_string_test ())
-    die ("strings");
-  
   printf ("%s: running linked list tests\n", argv[0]);
   if (!_dbus_list_test ())
     die ("lists");
diff --git a/doc/dbus-sasl-profile.txt b/doc/dbus-sasl-profile.txt
new file mode 100644 (file)
index 0000000..f71ceb0
--- /dev/null
@@ -0,0 +1,231 @@
+
+D-BUS Authentication
+===
+
+This document defines a small plain-text protocol used to perform
+authentication and negotiate a security layer before the flow of D-BUS
+messages begins.  This protocol is intended to be a profile of the
+Simple Authentication and Session Layer [SASL].
+
+This document is loosely based on the POP3 SASL profile by John Myers.
+
+Conventions Used in this Document
+===
+
+In examples, "C:" and "S:" indicate lines sent by the client and
+server respectively.
+
+The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY"
+in this document are to be interpreted as defined in "Key words for
+use in RFCs to Indicate Requirement Levels" [RFC 2119]
+
+Overview
+===
+
+The protocol is a line-based protocol, where each line ends with
+\r\n. Each line begins with an all-caps ASCII command name containing
+only the character range [A-Z], a space, then any arguments for the
+command, then the \r\n ending the line. The protocol is
+case-sensitive.
+
+Commands from the client to the server are as follows:
+
+   AUTH [mechanism] [initial-response]
+
+   CANCEL
+
+   BEGIN
+
+   DATA <data in base 64 encoding>
+
+   ERROR [human-readable error explanation]
+
+From server to client are as follows:
+
+   MECHANISMS <space-separated list of mechanism names>
+
+   REJECTED
+   OK
+
+   DATA <data in base 64 encoding>
+
+   ERROR
+
+AUTH Command
+===
+  
+   If an AUTH command has no arguments, it is a request to list 
+   available mechanisms. The server SHOULD respond with a MECHANISMS 
+   command listing the mechanisms it understands. 
+
+   If an AUTH command specifies a mechanism, and the server supports
+   said mechanism, the server SHOULD begin exchanging SASL
+   challenge-response data with the client using DATA commands.
+
+   If the server does not support the mechanism given in the AUTH
+   command, it SHOULD send a MECHANISMS command listing the mechanisms
+   it does support. A MECHANISMS command implies that any
+   authentication in progress was rejected, as if REJECTED were also
+   sent. A server MAY send a REJECTED command instead of a MECHANISMS
+   command, though this is unhelpful.
+
+   If the [initial-response] argument is provided, it is intended for
+   use with mechanisms that have no initial challenge (or an empty
+   initial challenge), as if it were the argument to an initial DATA
+   command. If the selected mechanism has an initial challenge, the
+   server should reject authentication (send MECHANISMS or REJECTED).
+
+   If authentication succeeds after exchanging DATA commands, 
+   an OK command should be sent to the client. 
+
+   The first octet received by the client after the \r\n of the OK
+   command MUST be the first octet of the authenticated/encrypted 
+   stream of D-BUS messages.
+
+   The first octet received by the server after the \r\n of the BEGIN
+   command from the client MUST be the first octet of the
+   authenticated/encrypted stream of D-BUS messages.
+
+CANCEL Command
+===
+
+   At any time up to sending the BEGIN command, the client may 
+   send a CANCEL command. On receiving the CANCEL command, the 
+   server MUST send a REJECTED or MECHANISMS command and abort the 
+   current authentication exchange.
+
+DATA Command
+===
+
+   The DATA command may come from either client or server, and simply 
+   contains a base64-encoded block of data to be interpreted 
+   according to the SASL mechanism in use.
+
+BEGIN Command
+===
+
+   The BEGIN command acknowledges that the client has received an 
+   OK command from the server, and that the stream of messages
+   is about to begin. 
+
+   The first octet received by the server after the \r\n of the BEGIN
+   command from the client MUST be the first octet of the
+   authenticated/encrypted stream of D-BUS messages.
+
+MECHANISMS Command
+===
+
+   The MECHANISMS command has a space-separated list of 
+   available auth mechanisms as arguments. The MECHANISMS command
+   implies REJECTED if an authentication exchange is in progress; 
+   the current exchange MUST be considered rejected.
+
+REJECTED Command
+===
+
+   The REJECTED command indicates that the current authentication
+   exchange has failed, and further exchange of DATA is inappropriate.
+   The client would normally try another mechanism, or try providing
+   different responses to challenges.
+
+OK Command
+===
+
+   The OK command indicates that the client has been authenticated,
+   and that further communication will be a stream of D-BUS messages
+   (optionally encrypted, as negotiated) rather than this protocol.
+
+   The first octet received by the client after the \r\n of the OK
+   command MUST be the first octet of the authenticated/encrypted 
+   stream of D-BUS messages.
+
+   The client MUST respond to the OK command by sending a BEGIN
+   command, followed by its stream of messages, or by disconnecting.
+   The server MUST NOT accept additional commands using this protocol 
+   after the OK command has been sent.
+
+ERROR Command
+===
+
+   The ERROR command indicates that either server or client did not
+   know a command, does not accept the given command in the current
+   context, or did not understand the arguments to the command. This
+   allows the protocol to be extended; a client or server can send a
+   command present or permitted only in new protocol versions, and if
+   an ERROR is received instead of an appropriate response, fall back
+   to using some other technique.
+   If an ERROR is sent, the server or client MUST continue as if the
+   command causing the ERROR had never been received.
+
+Example of successful magic cookie authentication
+===
+
+(MAGIC_COOKIE is a made up mechanism)
+
+  C: AUTH MAGIC_COOKIE BsAY3g4gBNo=
+  S: OK
+  C: BEGIN
+  
+Example of finding out mechanisms then picking one
+===
+
+  C: AUTH
+  S: MECHANISMS KERBEROS_V4 SKEY
+  C: AUTH SKEY bW9yZ2Fu
+  S: DATA OTUgUWE1ODMwOA==
+  C: DATA Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA==
+  S: OK
+  C: BEGIN
+
+Example of client sends unknown command then falls back to regular auth
+===
+
+  C: FOOBAR
+  S: ERROR
+  C: AUTH MAGIC_COOKIE BsAY3g4gBNo=
+  S: OK
+  C: BEGIN
+
+Example of server doesn't support initial auth mechanism
+===
+
+  C: AUTH MAGIC_COOKIE BsAY3g4gBNo=
+  S: MECHANISMS KERBEROS_V4 SKEY
+  C: AUTH SKEY bW9yZ2Fu
+  S: DATA OTUgUWE1ODMwOA==
+  C: DATA Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA==
+  S: OK
+  C: BEGIN
+
+Example of wrong password or the like followed by successful retry
+===
+
+  C: AUTH MAGIC_COOKIE BsAY3g4gBNo=
+  S: MECHANISMS KERBEROS_V4 SKEY
+  C: AUTH SKEY bW9yZ2Fu
+  S: DATA OTUgUWE1ODMwOA==
+  C: DATA Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA==
+  S: REJECTED
+  C: AUTH SKEY bW9yZ2Fu
+  S: DATA OTUgUWE1ODMwOA==
+  C: DATA Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA==
+  S: OK
+  C: BEGIN
+
+Example of skey canceled and restarted
+===
+
+  C: AUTH MAGIC_COOKIE BsAY3g4gBNo=
+  S: MECHANISMS KERBEROS_V4 SKEY
+  C: AUTH SKEY bW9yZ2Fu
+  S: DATA OTUgUWE1ODMwOA==
+  C: CANCEL
+  S: REJECTED
+  C: AUTH SKEY bW9yZ2Fu
+  S: DATA OTUgUWE1ODMwOA==
+  C: DATA Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA==
+  S: OK
+  C: BEGIN
+