summaryrefslogtreecommitdiffstats
path: root/src/3rdparty/libdbus/dbus/dbus-auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/3rdparty/libdbus/dbus/dbus-auth.c')
-rw-r--r--src/3rdparty/libdbus/dbus/dbus-auth.c2970
1 files changed, 2970 insertions, 0 deletions
diff --git a/src/3rdparty/libdbus/dbus/dbus-auth.c b/src/3rdparty/libdbus/dbus/dbus-auth.c
new file mode 100644
index 00000000..09942f80
--- /dev/null
+++ b/src/3rdparty/libdbus/dbus/dbus-auth.c
@@ -0,0 +1,2970 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/* dbus-auth.c Authentication
+ *
+ * Copyright (C) 2002, 2003, 2004 Red Hat Inc.
+ *
+ * SPDX-License-Identifier: AFL-2.1 OR GPL-2.0-or-later
+ *
+ * Licensed under the Academic Free License version 2.1
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <config.h>
+#include "dbus-auth.h"
+#include "dbus-string.h"
+#include "dbus-list.h"
+#include "dbus-internals.h"
+#include "dbus-keyring.h"
+#include "dbus-sha.h"
+#include "dbus-protocol.h"
+#include "dbus-credentials.h"
+
+/**
+ * @defgroup DBusAuth Authentication
+ * @ingroup DBusInternals
+ * @brief DBusAuth object
+ *
+ * DBusAuth manages the authentication negotiation when a connection
+ * is first established, and also manages any encryption used over a
+ * connection.
+ *
+ * @todo some SASL profiles require sending the empty string as a
+ * challenge/response, but we don't currently allow that in our
+ * protocol.
+ *
+ * @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
+ */
+
+/**
+ * @defgroup DBusAuthInternals Authentication implementation details
+ * @ingroup DBusInternals
+ * @brief DBusAuth implementation details
+ *
+ * Private details of authentication code.
+ *
+ * @{
+ */
+
+/**
+ * This function appends an initial client response to the given string
+ */
+typedef dbus_bool_t (* DBusInitialResponseFunction) (DBusAuth *auth,
+ DBusString *response);
+
+/**
+ * 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);
+
+/**
+ * Virtual table representing a particular auth mechanism.
+ */
+typedef struct
+{
+ const char *mechanism; /**< Name of the mechanism */
+ DBusAuthDataFunction server_data_func; /**< Function on server side for DATA */
+ DBusAuthEncodeFunction server_encode_func; /**< Function on server side to encode */
+ DBusAuthDecodeFunction server_decode_func; /**< Function on server side to decode */
+ DBusAuthShutdownFunction server_shutdown_func; /**< Function on server side to shut down */
+ DBusInitialResponseFunction client_initial_response_func; /**< Function on client side to handle initial response */
+ DBusAuthDataFunction client_data_func; /**< Function on client side for DATA */
+ DBusAuthEncodeFunction client_encode_func; /**< Function on client side for encode */
+ DBusAuthDecodeFunction client_decode_func; /**< Function on client side for decode */
+ DBusAuthShutdownFunction client_shutdown_func; /**< Function on client side for shutdown */
+} DBusAuthMechanismHandler;
+
+/**
+ * Enumeration for the known authentication commands.
+ */
+typedef enum {
+ DBUS_AUTH_COMMAND_AUTH,
+ DBUS_AUTH_COMMAND_CANCEL,
+ DBUS_AUTH_COMMAND_DATA,
+ DBUS_AUTH_COMMAND_BEGIN,
+ DBUS_AUTH_COMMAND_REJECTED,
+ DBUS_AUTH_COMMAND_OK,
+ DBUS_AUTH_COMMAND_ERROR,
+ DBUS_AUTH_COMMAND_UNKNOWN,
+ DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD,
+ DBUS_AUTH_COMMAND_AGREE_UNIX_FD
+} DBusAuthCommand;
+
+/**
+ * Auth state function, determines the reaction to incoming events for
+ * a particular state. Returns whether we had enough memory to
+ * complete the operation.
+ */
+typedef dbus_bool_t (* DBusAuthStateFunction) (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+
+/**
+ * Information about a auth state.
+ */
+typedef struct
+{
+ const char *name; /**< Name of the state */
+ DBusAuthStateFunction handler; /**< State function for this state */
+} DBusAuthStateData;
+
+/**
+ * Internal members of DBusAuth.
+ */
+struct DBusAuth
+{
+ int refcount; /**< reference count */
+ const char *side; /**< Client or server */
+
+ DBusString incoming; /**< Incoming data buffer */
+ DBusString outgoing; /**< Outgoing data buffer */
+
+ const DBusAuthStateData *state; /**< Current protocol state */
+
+ const DBusAuthMechanismHandler *mech; /**< Current auth mechanism */
+
+ DBusString identity; /**< Current identity we're authorizing
+ * as.
+ */
+
+ DBusCredentials *credentials; /**< Credentials read from socket
+ */
+
+ 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 */
+
+ char **allowed_mechs; /**< Mechanisms we're allowed to use,
+ * or #NULL if we can use any
+ */
+
+ unsigned int needed_memory : 1; /**< We needed memory to continue since last
+ * successful getting something done
+ */
+ 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 */
+
+ unsigned int unix_fd_possible : 1; /**< This side could do unix fd passing */
+ unsigned int unix_fd_negotiated : 1; /**< Unix fd was successfully negotiated */
+};
+
+/**
+ * "Subclass" of DBusAuth for client side
+ */
+typedef struct
+{
+ DBusAuth base; /**< Parent class */
+
+ DBusList *mechs_to_try; /**< Mechanisms we got from the server that we're going to try using */
+
+ DBusString guid_from_server; /**< GUID received from server */
+
+} DBusAuthClient;
+
+/**
+ * "Subclass" of DBusAuth for server side.
+ */
+typedef struct
+{
+ DBusAuth base; /**< Parent class */
+
+ int failures; /**< Number of times client has been rejected */
+ int max_failures; /**< Number of times we reject before disconnect */
+
+ DBusString guid; /**< Our globally unique ID in hex encoding */
+
+} DBusAuthServer;
+
+static void goto_state (DBusAuth *auth,
+ const DBusAuthStateData *new_state);
+static dbus_bool_t send_auth (DBusAuth *auth,
+ const DBusAuthMechanismHandler *mech);
+static dbus_bool_t send_data (DBusAuth *auth,
+ DBusString *data);
+static dbus_bool_t send_rejected (DBusAuth *auth);
+static dbus_bool_t send_error (DBusAuth *auth,
+ const char *message);
+static dbus_bool_t send_ok (DBusAuth *auth);
+static dbus_bool_t send_begin (DBusAuth *auth);
+static dbus_bool_t send_cancel (DBusAuth *auth);
+static dbus_bool_t send_negotiate_unix_fd (DBusAuth *auth);
+static dbus_bool_t send_agree_unix_fd (DBusAuth *auth);
+
+/**
+ * Client states
+ */
+
+static dbus_bool_t handle_server_state_waiting_for_auth (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+static dbus_bool_t handle_server_state_waiting_for_data (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+static dbus_bool_t handle_server_state_waiting_for_begin (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+
+static const DBusAuthStateData server_state_waiting_for_auth = {
+ "WaitingForAuth", handle_server_state_waiting_for_auth
+};
+static const DBusAuthStateData server_state_waiting_for_data = {
+ "WaitingForData", handle_server_state_waiting_for_data
+};
+static const DBusAuthStateData server_state_waiting_for_begin = {
+ "WaitingForBegin", handle_server_state_waiting_for_begin
+};
+
+/**
+ * Client states
+ */
+
+static dbus_bool_t handle_client_state_waiting_for_data (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+static dbus_bool_t handle_client_state_waiting_for_ok (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+static dbus_bool_t handle_client_state_waiting_for_reject (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+static dbus_bool_t handle_client_state_waiting_for_agree_unix_fd (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args);
+
+static const DBusAuthStateData client_state_need_send_auth = {
+ "NeedSendAuth", NULL
+};
+static const DBusAuthStateData client_state_waiting_for_data = {
+ "WaitingForData", handle_client_state_waiting_for_data
+};
+/* The WaitingForOK state doesn't appear to be used.
+ * See https://bugs.freedesktop.org/show_bug.cgi?id=97298 */
+_DBUS_GNUC_UNUSED
+static const DBusAuthStateData client_state_waiting_for_ok = {
+ "WaitingForOK", handle_client_state_waiting_for_ok
+};
+static const DBusAuthStateData client_state_waiting_for_reject = {
+ "WaitingForReject", handle_client_state_waiting_for_reject
+};
+static const DBusAuthStateData client_state_waiting_for_agree_unix_fd = {
+ "WaitingForAgreeUnixFD", handle_client_state_waiting_for_agree_unix_fd
+};
+
+/**
+ * Common terminal states. Terminal states have handler == NULL.
+ */
+
+static const DBusAuthStateData common_state_authenticated = {
+ "Authenticated", NULL
+};
+
+static const DBusAuthStateData common_state_need_disconnect = {
+ "NeedDisconnect", NULL
+};
+
+static const char auth_side_client[] = "client";
+static const char auth_side_server[] = "server";
+/**
+ * @param auth the auth conversation
+ * @returns #TRUE if the conversation is the server side
+ */
+#define DBUS_AUTH_IS_SERVER(auth) ((auth)->side == auth_side_server)
+/**
+ * @param auth the auth conversation
+ * @returns #TRUE if the conversation is the client side
+ */
+#define DBUS_AUTH_IS_CLIENT(auth) ((auth)->side == auth_side_client)
+/**
+ * @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))
+
+/**
+ * The name of the auth ("client" or "server")
+ * @param auth the auth conversation
+ * @returns a string
+ */
+#define DBUS_AUTH_NAME(auth) ((auth)->side)
+
+static DBusAuth*
+_dbus_auth_new (int size)
+{
+ DBusAuth *auth;
+
+ auth = dbus_malloc0 (size);
+ if (auth == NULL)
+ return NULL;
+
+ auth->refcount = 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
+ * 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))
+ goto enomem_0;
+
+ 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;
+
+ auth->credentials = _dbus_credentials_new ();
+ if (auth->credentials == NULL)
+ goto enomem_6;
+
+ auth->authorized_identity = _dbus_credentials_new ();
+ if (auth->authorized_identity == NULL)
+ goto enomem_7;
+
+ auth->desired_identity = _dbus_credentials_new ();
+ if (auth->desired_identity == NULL)
+ goto enomem_8;
+
+ return auth;
+
+#if 0
+ enomem_9:
+ _dbus_credentials_unref (auth->desired_identity);
+#endif
+ enomem_8:
+ _dbus_credentials_unref (auth->authorized_identity);
+ enomem_7:
+ _dbus_credentials_unref (auth->credentials);
+ enomem_6:
+ /* last alloc was an append to context, which is freed already below */ ;
+ 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
+shutdown_mech (DBusAuth *auth)
+{
+ /* Cancel any auth */
+ auth->already_asked_for_initial_response = FALSE;
+ _dbus_string_set_length (&auth->identity, 0);
+
+ _dbus_credentials_clear (auth->authorized_identity);
+ _dbus_credentials_clear (auth->desired_identity);
+
+ if (auth->mech != NULL)
+ {
+ _dbus_verbose ("%s: Shutting down mechanism %s\n",
+ DBUS_AUTH_NAME (auth), auth->mech->mechanism);
+
+ if (DBUS_AUTH_IS_CLIENT (auth))
+ (* auth->mech->client_shutdown_func) (auth);
+ else
+ (* auth->mech->server_shutdown_func) (auth);
+
+ auth->mech = NULL;
+ }
+}
+
+/*
+ * DBUS_COOKIE_SHA1 mechanism
+ */
+
+/* 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
+sha1_compute_hash (DBusAuth *auth,
+ int cookie_id,
+ const DBusString *server_challenge,
+ const DBusString *client_challenge,
+ DBusString *hash)
+{
+ DBusString cookie;
+ DBusString to_hash;
+ dbus_bool_t retval;
+
+ _dbus_assert (auth->keyring != NULL);
+
+ retval = FALSE;
+
+ if (!_dbus_string_init (&cookie))
+ return FALSE;
+
+ 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;
+
+ 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;
+}
+
+/** 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 = _DBUS_STRING_INIT_INVALID;
+ DBusString tmp2 = _DBUS_STRING_INIT_INVALID;
+ dbus_bool_t retval = FALSE;
+ DBusError error = DBUS_ERROR_INIT;
+ DBusCredentials *myself = NULL;
+
+ _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 ("%s: client tried to send auth identity, but we already have one\n",
+ DBUS_AUTH_NAME (auth));
+ return send_rejected (auth);
+ }
+ else
+ {
+ /* this is our auth identity */
+ if (!_dbus_string_copy (data, 0, &auth->identity, 0))
+ return FALSE;
+ }
+ }
+
+ if (!_dbus_credentials_add_from_user (auth->desired_identity, data,
+ DBUS_CREDENTIALS_ADD_FLAGS_USER_DATABASE,
+ &error))
+ {
+ if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
+ {
+ dbus_error_free (&error);
+ return FALSE;
+ }
+
+ _dbus_verbose ("%s: Did not get a valid username from client: %s\n",
+ DBUS_AUTH_NAME (auth), error.message);
+ dbus_error_free (&error);
+ return send_rejected (auth);
+ }
+
+ if (!_dbus_string_init (&tmp))
+ return FALSE;
+
+ if (!_dbus_string_init (&tmp2))
+ {
+ _dbus_string_free (&tmp);
+ return FALSE;
+ }
+
+ myself = _dbus_credentials_new_from_current_process ();
+
+ if (myself == NULL)
+ goto out;
+
+ if (!_dbus_credentials_same_user (myself, auth->desired_identity))
+ {
+ /*
+ * DBUS_COOKIE_SHA1 is not suitable for authenticating that the
+ * client is anyone other than the user owning the process
+ * containing the DBusServer: we probably aren't allowed to write
+ * to other users' home directories. Even if we can (for example
+ * uid 0 on traditional Unix or CAP_DAC_OVERRIDE on Linux), we
+ * must not, because the other user controls their home directory,
+ * and could carry out symlink attacks to make us read from or
+ * write to unintended locations. It's difficult to avoid symlink
+ * attacks in a portable way, so we just don't try. This isn't a
+ * regression, because DBUS_COOKIE_SHA1 never worked for other
+ * users anyway.
+ */
+ _dbus_verbose ("%s: client tried to authenticate as \"%s\", "
+ "but that doesn't match this process",
+ DBUS_AUTH_NAME (auth),
+ _dbus_string_get_const_data (data));
+ retval = send_rejected (auth);
+ goto out;
+ }
+
+ /* 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_credentials (auth->keyring,
+ auth->desired_identity))
+ {
+ _dbus_keyring_unref (auth->keyring);
+ auth->keyring = NULL;
+ }
+
+ if (auth->keyring == NULL)
+ {
+ auth->keyring = _dbus_keyring_new_for_credentials (auth->desired_identity,
+ &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 ("%s: Error loading keyring: %s\n",
+ DBUS_AUTH_NAME (auth), 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);
+
+ auth->cookie_id = _dbus_keyring_get_best_key (auth->keyring, &error);
+ if (auth->cookie_id < 0)
+ {
+ _DBUS_ASSERT_ERROR_IS_SET (&error);
+ _dbus_verbose ("%s: Could not get a cookie ID to send to client: %s\n",
+ DBUS_AUTH_NAME (auth), 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, &error))
+ {
+ 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 ("%s: Error generating challenge: %s\n",
+ DBUS_AUTH_NAME (auth), error.message);
+ if (send_rejected (auth))
+ retval = TRUE; /* retval is only about mem */
+
+ dbus_error_free (&error);
+ 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 (!send_data (auth, &tmp2))
+ goto out;
+
+ goto_state (auth, &server_state_waiting_for_data);
+ retval = TRUE;
+
+ out:
+ _dbus_string_zero (&tmp);
+ _dbus_string_free (&tmp);
+ _dbus_string_zero (&tmp2);
+ _dbus_string_free (&tmp2);
+ _dbus_clear_credentials (&myself);
+
+ return retval;
+}
+
+static dbus_bool_t
+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;
+
+ retval = FALSE;
+
+ if (!_dbus_string_find_blank (data, 0, &i))
+ {
+ _dbus_verbose ("%s: no space separator in client response\n",
+ DBUS_AUTH_NAME (auth));
+ 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 ("%s: zero-length client challenge or hash\n",
+ DBUS_AUTH_NAME (auth));
+ 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_credentials_add_credentials (auth->authorized_identity,
+ auth->desired_identity))
+ goto out_3;
+
+ /* Copy process ID (and PID FD) from the socket credentials if it's there
+ */
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
+ auth->credentials))
+ goto out_3;
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_ID,
+ auth->credentials))
+ goto out_3;
+
+ if (!send_ok (auth))
+ goto out_3;
+
+ _dbus_verbose ("%s: authenticated client using DBUS_COOKIE_SHA1\n",
+ DBUS_AUTH_NAME (auth));
+
+ 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 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);
+}
+
+static dbus_bool_t
+handle_client_initial_response_cookie_sha1_mech (DBusAuth *auth,
+ DBusString *response)
+{
+ DBusString username;
+ dbus_bool_t retval;
+
+ retval = FALSE;
+
+ if (!_dbus_string_init (&username))
+ return FALSE;
+
+ if (!_dbus_append_user_from_current_process (&username))
+ goto out_0;
+
+ if (!_dbus_string_hex_encode (&username, 0,
+ response,
+ _dbus_string_get_length (response)))
+ goto out_0;
+
+ retval = TRUE;
+
+ out_0:
+ _dbus_string_free (&username);
+
+ return retval;
+}
+
+static dbus_bool_t
+handle_client_data_cookie_sha1_mech (DBusAuth *auth,
+ const DBusString *data)
+{
+ /* 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 = FALSE;
+ DBusString context;
+ DBusString cookie_id_str;
+ DBusString server_challenge;
+ DBusString client_challenge;
+ DBusString correct_hash;
+ DBusString tmp;
+ int i, j;
+ long val;
+ DBusError error = DBUS_ERROR_INIT;
+
+ if (!_dbus_string_find_blank (data, 0, &i))
+ {
+ if (send_error (auth,
+ "Server did not send context/ID/challenge properly"))
+ 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 (send_error (auth,
+ "Server did not send context/ID/challenge properly"))
+ 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 (send_error (auth, "Server sent invalid cookie context"))
+ retval = TRUE;
+ goto out_3;
+ }
+
+ if (!_dbus_string_parse_int (&cookie_id_str, 0, &val, NULL))
+ {
+ if (send_error (auth, "Could not parse cookie ID as an integer"))
+ retval = TRUE;
+ goto out_3;
+ }
+
+ if (_dbus_string_get_length (&server_challenge) == 0)
+ {
+ if (send_error (auth, "Empty server challenge string"))
+ retval = TRUE;
+ goto out_3;
+ }
+
+ if (auth->keyring == NULL)
+ {
+ auth->keyring = _dbus_keyring_new_for_credentials (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 ("%s: Error loading keyring: %s\n",
+ DBUS_AUTH_NAME (auth), error.message);
+
+ if (send_error (auth, "Could not load cookie file"))
+ 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, &error))
+ {
+ if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
+ {
+ dbus_error_free (&error);
+ goto out_4;
+ }
+ else
+ {
+ _DBUS_ASSERT_ERROR_IS_SET (&error);
+
+ _dbus_verbose ("%s: Failed to generate challenge: %s\n",
+ DBUS_AUTH_NAME (auth), error.message);
+
+ if (send_error (auth, "Failed to generate challenge"))
+ retval = TRUE; /* retval is only about mem */
+
+ dbus_error_free (&error);
+ 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_5;
+
+ 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 (send_error (auth, "Don't have the requested cookie ID"))
+ 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;
+
+ if (!send_data (auth, &tmp))
+ 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);
+}
+
+/*
+ * EXTERNAL mechanism
+ */
+
+static dbus_bool_t
+handle_server_data_external_mech (DBusAuth *auth,
+ const DBusString *data)
+{
+ if (_dbus_credentials_are_anonymous (auth->credentials))
+ {
+ _dbus_verbose ("%s: no credentials, mechanism EXTERNAL can't authenticate\n",
+ DBUS_AUTH_NAME (auth));
+ return send_rejected (auth);
+ }
+
+ if (_dbus_string_get_length (data) > 0)
+ {
+ if (_dbus_string_get_length (&auth->identity) > 0)
+ {
+ /* Tried to send two auth identities, wtf */
+ _dbus_verbose ("%s: client tried to send auth identity, but we already have one\n",
+ DBUS_AUTH_NAME (auth));
+ return send_rejected (auth);
+ }
+ else
+ {
+ /* this is our auth identity */
+ if (!_dbus_string_copy (data, 0, &auth->identity, 0))
+ return FALSE;
+ }
+ }
+
+ /* Poke client for an auth identity, if none given */
+ if (_dbus_string_get_length (&auth->identity) == 0 &&
+ !auth->already_asked_for_initial_response)
+ {
+ if (send_data (auth, NULL))
+ {
+ _dbus_verbose ("%s: sending empty challenge asking client for auth identity\n",
+ DBUS_AUTH_NAME (auth));
+ auth->already_asked_for_initial_response = TRUE;
+ goto_state (auth, &server_state_waiting_for_data);
+ return TRUE;
+ }
+ else
+ return FALSE;
+ }
+
+ _dbus_credentials_clear (auth->desired_identity);
+
+ /* If auth->identity is still empty here, then client
+ * responded with an empty string after we poked it for
+ * an initial response. This means to try to auth the
+ * identity provided in the credentials.
+ */
+ if (_dbus_string_get_length (&auth->identity) == 0)
+ {
+ if (!_dbus_credentials_add_credentials (auth->desired_identity,
+ auth->credentials))
+ {
+ return FALSE; /* OOM */
+ }
+ }
+ else
+ {
+ DBusError error = DBUS_ERROR_INIT;
+
+ if (!_dbus_credentials_add_from_user (auth->desired_identity,
+ &auth->identity,
+ DBUS_CREDENTIALS_ADD_FLAGS_NONE,
+ &error))
+ {
+ if (dbus_error_has_name (&error, DBUS_ERROR_NO_MEMORY))
+ {
+ dbus_error_free (&error);
+ return FALSE;
+ }
+
+ _dbus_verbose ("%s: could not get credentials from uid string: %s\n",
+ DBUS_AUTH_NAME (auth), error.message);
+ dbus_error_free (&error);
+ return send_rejected (auth);
+ }
+ }
+
+ if (_dbus_credentials_are_anonymous (auth->desired_identity))
+ {
+ _dbus_verbose ("%s: desired user %s is no good\n",
+ DBUS_AUTH_NAME (auth),
+ _dbus_string_get_const_data (&auth->identity));
+ return send_rejected (auth);
+ }
+
+ if (_dbus_credentials_are_superset (auth->credentials,
+ auth->desired_identity))
+ {
+ /* client has authenticated */
+ if (!_dbus_credentials_add_credentials (auth->authorized_identity,
+ auth->desired_identity))
+ return FALSE;
+
+ /* also copy misc process info from the socket credentials
+ */
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
+ auth->credentials))
+ return FALSE;
+
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_ID,
+ auth->credentials))
+ return FALSE;
+
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID,
+ auth->credentials))
+ return FALSE;
+
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_GROUP_IDS,
+ auth->credentials))
+ return FALSE;
+
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_LINUX_SECURITY_LABEL,
+ auth->credentials))
+ return FALSE;
+
+ if (!send_ok (auth))
+ return FALSE;
+
+ _dbus_verbose ("%s: authenticated client based on socket credentials\n",
+ DBUS_AUTH_NAME (auth));
+
+ return TRUE;
+ }
+ else
+ {
+ _dbus_verbose ("%s: desired identity not found in socket credentials\n",
+ DBUS_AUTH_NAME (auth));
+ return send_rejected (auth);
+ }
+}
+
+static void
+handle_server_shutdown_external_mech (DBusAuth *auth)
+{
+
+}
+
+static dbus_bool_t
+handle_client_initial_response_external_mech (DBusAuth *auth,
+ DBusString *response)
+{
+ /* We always append our UID as an initial response, so the server
+ * doesn't have to send back an empty challenge to check whether we
+ * want to specify an identity. i.e. this avoids a round trip that
+ * the spec for the EXTERNAL mechanism otherwise requires.
+ */
+ DBusString plaintext;
+
+ if (!_dbus_string_init (&plaintext))
+ return FALSE;
+
+ if (!_dbus_append_user_from_current_process (&plaintext))
+ goto failed;
+
+ if (!_dbus_string_hex_encode (&plaintext, 0,
+ response,
+ _dbus_string_get_length (response)))
+ goto failed;
+
+ _dbus_string_free (&plaintext);
+
+ return TRUE;
+
+ failed:
+ _dbus_string_free (&plaintext);
+ return FALSE;
+}
+
+static dbus_bool_t
+handle_client_data_external_mech (DBusAuth *auth,
+ const DBusString *data)
+{
+
+ return TRUE;
+}
+
+static void
+handle_client_shutdown_external_mech (DBusAuth *auth)
+{
+
+}
+
+/*
+ * ANONYMOUS mechanism
+ */
+
+static dbus_bool_t
+handle_server_data_anonymous_mech (DBusAuth *auth,
+ const DBusString *data)
+{
+ if (_dbus_string_get_length (data) > 0)
+ {
+ /* Client is allowed to send "trace" data, the only defined
+ * meaning is that if it contains '@' it is an email address,
+ * and otherwise it is anything else, and it's supposed to be
+ * UTF-8
+ */
+ if (!_dbus_string_validate_utf8 (data, 0, _dbus_string_get_length (data)))
+ {
+ _dbus_verbose ("%s: Received invalid UTF-8 trace data from ANONYMOUS client\n",
+ DBUS_AUTH_NAME (auth));
+ return send_rejected (auth);
+ }
+
+ _dbus_verbose ("%s: ANONYMOUS client sent trace string: '%s'\n",
+ DBUS_AUTH_NAME (auth),
+ _dbus_string_get_const_data (data));
+ }
+
+ /* We want to be anonymous (clear in case some other protocol got midway through I guess) */
+ _dbus_credentials_clear (auth->desired_identity);
+
+ /* Copy process ID (and PID FD) from the socket credentials
+ */
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_FD,
+ auth->credentials))
+ return FALSE;
+
+ if (!_dbus_credentials_add_credential (auth->authorized_identity,
+ DBUS_CREDENTIAL_UNIX_PROCESS_ID,
+ auth->credentials))
+ return FALSE;
+
+ /* Anonymous is always allowed */
+ if (!send_ok (auth))
+ return FALSE;
+
+ _dbus_verbose ("%s: authenticated client as anonymous\n",
+ DBUS_AUTH_NAME (auth));
+
+ return TRUE;
+}
+
+static void
+handle_server_shutdown_anonymous_mech (DBusAuth *auth)
+{
+
+}
+
+static dbus_bool_t
+handle_client_initial_response_anonymous_mech (DBusAuth *auth,
+ DBusString *response)
+{
+ /* Our initial response is a "trace" string which must be valid UTF-8
+ * and must be an email address if it contains '@'.
+ * We just send the dbus implementation info, like a user-agent or
+ * something, because... why not. There's nothing guaranteed here
+ * though, we could change it later.
+ */
+ DBusString plaintext;
+
+ if (!_dbus_string_init (&plaintext))
+ return FALSE;
+
+ if (!_dbus_string_append (&plaintext,
+ "libdbus " DBUS_VERSION_STRING))
+ goto failed;
+
+ if (!_dbus_string_hex_encode (&plaintext, 0,
+ response,
+ _dbus_string_get_length (response)))
+ goto failed;
+
+ _dbus_string_free (&plaintext);
+
+ return TRUE;
+
+ failed:
+ _dbus_string_free (&plaintext);
+ return FALSE;
+}
+
+static dbus_bool_t
+handle_client_data_anonymous_mech (DBusAuth *auth,
+ const DBusString *data)
+{
+
+ return TRUE;
+}
+
+static void
+handle_client_shutdown_anonymous_mech (DBusAuth *auth)
+{
+
+}
+
+/* Put mechanisms here in order of preference.
+ * Right now we have:
+ *
+ * - EXTERNAL checks socket credentials (or in the future, other info from the OS)
+ * - DBUS_COOKIE_SHA1 uses a cookie in the home directory, like xauth or ICE
+ * - ANONYMOUS checks nothing but doesn't auth the person as a user
+ *
+ * We might ideally add a mechanism to chain to Cyrus SASL so we can
+ * use its mechanisms as well.
+ *
+ */
+static const DBusAuthMechanismHandler
+all_mechanisms[] = {
+ { "EXTERNAL",
+ handle_server_data_external_mech,
+ NULL, NULL,
+ handle_server_shutdown_external_mech,
+ handle_client_initial_response_external_mech,
+ handle_client_data_external_mech,
+ NULL, NULL,
+ handle_client_shutdown_external_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 },
+ { "ANONYMOUS",
+ handle_server_data_anonymous_mech,
+ NULL, NULL,
+ handle_server_shutdown_anonymous_mech,
+ handle_client_initial_response_anonymous_mech,
+ handle_client_data_anonymous_mech,
+ NULL, NULL,
+ handle_client_shutdown_anonymous_mech },
+ { NULL, NULL }
+};
+
+static const DBusAuthMechanismHandler*
+find_mech (const DBusString *name,
+ char **allowed_mechs)
+{
+ int i;
+
+ if (allowed_mechs != NULL &&
+ !_dbus_string_array_contains ((const char**) allowed_mechs,
+ _dbus_string_get_const_data (name)))
+ return NULL;
+
+ 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_auth (DBusAuth *auth, const DBusAuthMechanismHandler *mech)
+{
+ DBusString auth_command;
+
+ if (!_dbus_string_init (&auth_command))
+ return FALSE;
+
+ if (!_dbus_string_append (&auth_command,
+ "AUTH "))
+ {
+ _dbus_string_free (&auth_command);
+ return FALSE;
+ }
+
+ if (!_dbus_string_append (&auth_command,
+ mech->mechanism))
+ {
+ _dbus_string_free (&auth_command);
+ return FALSE;
+ }
+
+ if (mech->client_initial_response_func != NULL)
+ {
+ if (!_dbus_string_append (&auth_command, " "))
+ {
+ _dbus_string_free (&auth_command);
+ return FALSE;
+ }
+
+ if (!(* mech->client_initial_response_func) (auth, &auth_command))
+ {
+ _dbus_string_free (&auth_command);
+ return FALSE;
+ }
+ }
+
+ if (!_dbus_string_append (&auth_command,
+ "\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_string_free (&auth_command);
+ shutdown_mech (auth);
+ auth->mech = mech;
+ goto_state (auth, &client_state_waiting_for_data);
+
+ return TRUE;
+}
+
+static dbus_bool_t
+send_data (DBusAuth *auth, DBusString *data)
+{
+ int old_len;
+
+ if (data == NULL || _dbus_string_get_length (data) == 0)
+ return _dbus_string_append (&auth->outgoing, "DATA\r\n");
+ else
+ {
+ old_len = _dbus_string_get_length (&auth->outgoing);
+ if (!_dbus_string_append (&auth->outgoing, "DATA "))
+ goto out;
+
+ if (!_dbus_string_hex_encode (data, 0, &auth->outgoing,
+ _dbus_string_get_length (&auth->outgoing)))
+ goto out;
+
+ if (!_dbus_string_append (&auth->outgoing, "\r\n"))
+ goto out;
+
+ return TRUE;
+
+ out:
+ _dbus_string_set_length (&auth->outgoing, old_len);
+
+ return FALSE;
+ }
+}
+
+static dbus_bool_t
+send_rejected (DBusAuth *auth)
+{
+ DBusString command;
+ DBusAuthServer *server_auth;
+ int i;
+
+ if (!_dbus_string_init (&command))
+ return FALSE;
+
+ if (!_dbus_string_append (&command,
+ "REJECTED"))
+ goto nomem;
+
+ for (i = 0; all_mechanisms[i].mechanism != NULL; i++)
+ {
+ /* skip mechanisms that aren't allowed */
+ if (auth->allowed_mechs != NULL &&
+ !_dbus_string_array_contains ((const char**)auth->allowed_mechs,
+ all_mechanisms[i].mechanism))
+ continue;
+
+ if (!_dbus_string_append (&command,
+ " "))
+ goto nomem;
+
+ if (!_dbus_string_append (&command,
+ all_mechanisms[i].mechanism))
+ goto nomem;
+ }
+
+ 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;
+
+ shutdown_mech (auth);
+
+ _dbus_assert (DBUS_AUTH_IS_SERVER (auth));
+ server_auth = DBUS_AUTH_SERVER (auth);
+ server_auth->failures += 1;
+
+ if (server_auth->failures >= server_auth->max_failures)
+ goto_state (auth, &common_state_need_disconnect);
+ else
+ goto_state (auth, &server_state_waiting_for_auth);
+
+ _dbus_string_free (&command);
+
+ return TRUE;
+
+ nomem:
+ _dbus_string_free (&command);
+ return FALSE;
+}
+
+static dbus_bool_t
+send_error (DBusAuth *auth, const char *message)
+{
+ return _dbus_string_append_printf (&auth->outgoing,
+ "ERROR \"%s\"\r\n", message);
+}
+
+static dbus_bool_t
+send_ok (DBusAuth *auth)
+{
+ int orig_len;
+
+ orig_len = _dbus_string_get_length (&auth->outgoing);
+
+ if (_dbus_string_append (&auth->outgoing, "OK ") &&
+ _dbus_string_copy (& DBUS_AUTH_SERVER (auth)->guid,
+ 0,
+ &auth->outgoing,
+ _dbus_string_get_length (&auth->outgoing)) &&
+ _dbus_string_append (&auth->outgoing, "\r\n"))
+ {
+ goto_state (auth, &server_state_waiting_for_begin);
+ return TRUE;
+ }
+ else
+ {
+ _dbus_string_set_length (&auth->outgoing, orig_len);
+ return FALSE;
+ }
+}
+
+static dbus_bool_t
+send_begin (DBusAuth *auth)
+{
+
+ if (!_dbus_string_append (&auth->outgoing,
+ "BEGIN\r\n"))
+ return FALSE;
+
+ goto_state (auth, &common_state_authenticated);
+ return TRUE;
+}
+
+static dbus_bool_t
+process_ok(DBusAuth *auth,
+ const DBusString *args_from_ok) {
+
+ int end_of_hex;
+
+ /* "args_from_ok" should be the GUID, whitespace already pulled off the front */
+ _dbus_assert (_dbus_string_get_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server) == 0);
+
+ /* We decode the hex string to binary, using guid_from_server as scratch... */
+
+ end_of_hex = 0;
+ if (!_dbus_string_hex_decode (args_from_ok, 0, &end_of_hex,
+ & DBUS_AUTH_CLIENT (auth)->guid_from_server, 0))
+ return FALSE;
+
+ /* now clear out the scratch */
+ _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0);
+
+ if (end_of_hex != _dbus_string_get_length (args_from_ok) ||
+ end_of_hex == 0)
+ {
+ _dbus_verbose ("Bad GUID from server, parsed %d bytes and had %d bytes from server\n",
+ end_of_hex, _dbus_string_get_length (args_from_ok));
+ goto_state (auth, &common_state_need_disconnect);
+ return TRUE;
+ }
+
+ if (!_dbus_string_copy (args_from_ok, 0, &DBUS_AUTH_CLIENT (auth)->guid_from_server, 0)) {
+ _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0);
+ return FALSE;
+ }
+
+ _dbus_verbose ("Got GUID '%s' from the server\n",
+ _dbus_string_get_const_data (& DBUS_AUTH_CLIENT (auth)->guid_from_server));
+
+ if (auth->unix_fd_possible)
+ {
+ if (!send_negotiate_unix_fd (auth))
+ {
+ _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0);
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+
+ _dbus_verbose("Not negotiating unix fd passing, since not possible\n");
+
+ if (!send_begin (auth))
+ {
+ _dbus_string_set_length (& DBUS_AUTH_CLIENT (auth)->guid_from_server, 0);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static dbus_bool_t
+send_cancel (DBusAuth *auth)
+{
+ if (_dbus_string_append (&auth->outgoing, "CANCEL\r\n"))
+ {
+ goto_state (auth, &client_state_waiting_for_reject);
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static dbus_bool_t
+process_data (DBusAuth *auth,
+ const DBusString *args,
+ DBusAuthDataFunction data_func)
+{
+ int end;
+ DBusString decoded;
+
+ if (!_dbus_string_init (&decoded))
+ return FALSE;
+
+ if (!_dbus_string_hex_decode (args, 0, &end, &decoded, 0))
+ {
+ _dbus_string_free (&decoded);
+ return FALSE;
+ }
+
+ if (_dbus_string_get_length (args) != end)
+ {
+ _dbus_string_free (&decoded);
+ if (!send_error (auth, "Invalid hex encoding"))
+ return FALSE;
+
+ return TRUE;
+ }
+
+#ifdef DBUS_ENABLE_VERBOSE_MODE
+ if (_dbus_string_validate_ascii (&decoded, 0,
+ _dbus_string_get_length (&decoded)))
+ _dbus_verbose ("%s: data: '%s'\n",
+ DBUS_AUTH_NAME (auth),
+ _dbus_string_get_const_data (&decoded));
+#endif
+
+ if (!(* data_func) (auth, &decoded))
+ {
+ _dbus_string_free (&decoded);
+ return FALSE;
+ }
+
+ _dbus_string_free (&decoded);
+ return TRUE;
+}
+
+static dbus_bool_t
+send_negotiate_unix_fd (DBusAuth *auth)
+{
+ if (!_dbus_string_append (&auth->outgoing,
+ "NEGOTIATE_UNIX_FD\r\n"))
+ return FALSE;
+
+ goto_state (auth, &client_state_waiting_for_agree_unix_fd);
+ return TRUE;
+}
+
+static dbus_bool_t
+send_agree_unix_fd (DBusAuth *auth)
+{
+ _dbus_assert(auth->unix_fd_possible);
+
+ auth->unix_fd_negotiated = TRUE;
+ _dbus_verbose("Agreed to UNIX FD passing\n");
+
+ if (!_dbus_string_append (&auth->outgoing,
+ "AGREE_UNIX_FD\r\n"))
+ return FALSE;
+
+ goto_state (auth, &server_state_waiting_for_begin);
+ return TRUE;
+}
+
+static dbus_bool_t
+handle_auth (DBusAuth *auth, const DBusString *args)
+{
+ if (_dbus_string_get_length (args) == 0)
+ {
+ /* No args to the auth, send mechanisms */
+ if (!send_rejected (auth))
+ return FALSE;
+
+ return TRUE;
+ }
+ else
+ {
+ int i;
+ DBusString mech;
+ DBusString hex_response;
+
+ _dbus_string_find_blank (args, 0, &i);
+
+ if (!_dbus_string_init (&mech))
+ return FALSE;
+
+ if (!_dbus_string_init (&hex_response))
+ {
+ _dbus_string_free (&mech);
+ return FALSE;
+ }
+
+ if (!_dbus_string_copy_len (args, 0, i, &mech, 0))
+ goto failed;
+
+ _dbus_string_skip_blank (args, i, &i);
+ if (!_dbus_string_copy (args, i, &hex_response, 0))
+ goto failed;
+
+ auth->mech = find_mech (&mech, auth->allowed_mechs);
+ if (auth->mech != NULL)
+ {
+ _dbus_verbose ("%s: Trying mechanism %s\n",
+ DBUS_AUTH_NAME (auth),
+ auth->mech->mechanism);
+
+ if (!process_data (auth, &hex_response,
+ auth->mech->server_data_func))
+ goto failed;
+ }
+ else
+ {
+ /* Unsupported mechanism */
+ _dbus_verbose ("%s: Unsupported mechanism %s\n",
+ DBUS_AUTH_NAME (auth),
+ _dbus_string_get_const_data (&mech));
+
+ if (!send_rejected (auth))
+ goto failed;
+ }
+
+ _dbus_string_free (&mech);
+ _dbus_string_free (&hex_response);
+
+ return TRUE;
+
+ failed:
+ auth->mech = NULL;
+ _dbus_string_free (&mech);
+ _dbus_string_free (&hex_response);
+ return FALSE;
+ }
+}
+
+static dbus_bool_t
+handle_server_state_waiting_for_auth (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args)
+{
+ switch (command)
+ {
+ case DBUS_AUTH_COMMAND_AUTH:
+ return handle_auth (auth, args);
+
+ case DBUS_AUTH_COMMAND_CANCEL:
+ case DBUS_AUTH_COMMAND_DATA:
+ return send_error (auth, "Not currently in an auth conversation");
+
+ case DBUS_AUTH_COMMAND_BEGIN:
+ goto_state (auth, &common_state_need_disconnect);
+ return TRUE;
+
+ case DBUS_AUTH_COMMAND_ERROR:
+ return send_rejected (auth);
+
+ case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD:
+ return send_error (auth, "Need to authenticate first");
+
+ case DBUS_AUTH_COMMAND_REJECTED:
+ case DBUS_AUTH_COMMAND_OK:
+ case DBUS_AUTH_COMMAND_UNKNOWN:
+ case DBUS_AUTH_COMMAND_AGREE_UNIX_FD:
+ default:
+ return send_error (auth, "Unknown command");
+ }
+}
+
+static dbus_bool_t
+handle_server_state_waiting_for_data (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args)
+{
+ switch (command)
+ {
+ case DBUS_AUTH_COMMAND_AUTH:
+ return send_error (auth, "Sent AUTH while another AUTH in progress");
+
+ case DBUS_AUTH_COMMAND_CANCEL:
+ case DBUS_AUTH_COMMAND_ERROR:
+ return send_rejected (auth);
+
+ case DBUS_AUTH_COMMAND_DATA:
+ return process_data (auth, args, auth->mech->server_data_func);
+
+ case DBUS_AUTH_COMMAND_BEGIN:
+ goto_state (auth, &common_state_need_disconnect);
+ return TRUE;
+
+ case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD:
+ return send_error (auth, "Need to authenticate first");
+
+ case DBUS_AUTH_COMMAND_REJECTED:
+ case DBUS_AUTH_COMMAND_OK:
+ case DBUS_AUTH_COMMAND_UNKNOWN:
+ case DBUS_AUTH_COMMAND_AGREE_UNIX_FD:
+ default:
+ return send_error (auth, "Unknown command");
+ }
+}
+
+static dbus_bool_t
+handle_server_state_waiting_for_begin (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args)
+{
+ switch (command)
+ {
+ case DBUS_AUTH_COMMAND_AUTH:
+ return send_error (auth, "Sent AUTH while expecting BEGIN");
+
+ case DBUS_AUTH_COMMAND_DATA:
+ return send_error (auth, "Sent DATA while expecting BEGIN");
+
+ case DBUS_AUTH_COMMAND_BEGIN:
+ goto_state (auth, &common_state_authenticated);
+ return TRUE;
+
+ case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD:
+ if (auth->unix_fd_possible)
+ return send_agree_unix_fd(auth);
+ else
+ return send_error(auth, "Unix FD passing not supported, not authenticated or otherwise not possible");
+
+ case DBUS_AUTH_COMMAND_REJECTED:
+ case DBUS_AUTH_COMMAND_OK:
+ case DBUS_AUTH_COMMAND_UNKNOWN:
+ case DBUS_AUTH_COMMAND_AGREE_UNIX_FD:
+ default:
+ return send_error (auth, "Unknown command");
+
+ case DBUS_AUTH_COMMAND_CANCEL:
+ case DBUS_AUTH_COMMAND_ERROR:
+ return send_rejected (auth);
+ }
+}
+
+/* return FALSE if no memory, TRUE if all OK */
+static dbus_bool_t
+get_word (const DBusString *str,
+ int *start,
+ DBusString *word)
+{
+ int i;
+
+ _dbus_string_skip_blank (str, *start, start);
+ _dbus_string_find_blank (str, *start, &i);
+
+ if (i > *start)
+ {
+ if (!_dbus_string_copy_len (str, *start, i - *start, word, 0))
+ return FALSE;
+
+ *start = i;
+ }
+
+ return TRUE;
+}
+
+static dbus_bool_t
+record_mechanisms (DBusAuth *auth,
+ 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))
+ goto nomem;
+
+ if (!get_word (args, &next, &m))
+ {
+ _dbus_string_free (&m);
+ goto nomem;
+ }
+
+ mech = find_mech (&m, auth->allowed_mechs);
+
+ 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 (mech != &all_mechanisms[0])
+ {
+ _dbus_verbose ("%s: Adding mechanism %s to list we will try\n",
+ DBUS_AUTH_NAME (auth), mech->mechanism);
+
+ if (!_dbus_list_append (& DBUS_AUTH_CLIENT (auth)->mechs_to_try,
+ (void*) mech))
+ {
+ _dbus_string_free (&m);
+ goto nomem;
+ }
+ }
+ else
+ {
+ _dbus_verbose ("%s: Already tried mechanism %s; not adding to list we will try\n",
+ DBUS_AUTH_NAME (auth), mech->mechanism);
+ }
+ }
+ else
+ {
+ _dbus_verbose ("%s: Server offered mechanism \"%s\" that we don't know how to use\n",
+ DBUS_AUTH_NAME (auth),
+ _dbus_string_get_const_data (&m));
+ }
+
+ _dbus_string_free (&m);
+ }
+
+ 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 *args)
+{
+ const DBusAuthMechanismHandler *mech;
+ DBusAuthClient *client;
+
+ client = DBUS_AUTH_CLIENT (auth);
+
+ if (!auth->already_got_mechanisms)
+ {
+ if (!record_mechanisms (auth, args))
+ return FALSE;
+ }
+
+ if (DBUS_AUTH_CLIENT (auth)->mechs_to_try != NULL)
+ {
+ mech = client->mechs_to_try->data;
+
+ if (!send_auth (auth, mech))
+ return FALSE;
+
+ _dbus_list_pop_first (&client->mechs_to_try);
+
+ _dbus_verbose ("%s: Trying mechanism %s\n",
+ DBUS_AUTH_NAME (auth),
+ mech->mechanism);
+ }
+ else
+ {
+ /* Give up */
+ _dbus_verbose ("%s: Disconnecting because we are out of mechanisms to try using\n",
+ DBUS_AUTH_NAME (auth));
+ goto_state (auth, &common_state_need_disconnect);
+ }
+
+ return TRUE;
+}
+
+
+static dbus_bool_t
+handle_client_state_waiting_for_data (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args)
+{
+ _dbus_assert (auth->mech != NULL);
+
+ switch (command)
+ {
+ case DBUS_AUTH_COMMAND_DATA:
+ return process_data (auth, args, auth->mech->client_data_func);
+
+ case DBUS_AUTH_COMMAND_REJECTED:
+ return process_rejected (auth, args);
+
+ case DBUS_AUTH_COMMAND_OK:
+ return process_ok(auth, args);
+
+ case DBUS_AUTH_COMMAND_ERROR:
+ return send_cancel (auth);
+
+ case DBUS_AUTH_COMMAND_AUTH:
+ case DBUS_AUTH_COMMAND_CANCEL:
+ case DBUS_AUTH_COMMAND_BEGIN:
+ case DBUS_AUTH_COMMAND_UNKNOWN:
+ case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD:
+ case DBUS_AUTH_COMMAND_AGREE_UNIX_FD:
+ default:
+ return send_error (auth, "Unknown command");
+ }
+}
+
+static dbus_bool_t
+handle_client_state_waiting_for_ok (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args)
+{
+ switch (command)
+ {
+ case DBUS_AUTH_COMMAND_REJECTED:
+ return process_rejected (auth, args);
+
+ case DBUS_AUTH_COMMAND_OK:
+ return process_ok(auth, args);
+
+ case DBUS_AUTH_COMMAND_DATA:
+ case DBUS_AUTH_COMMAND_ERROR:
+ return send_cancel (auth);
+
+ case DBUS_AUTH_COMMAND_AUTH:
+ case DBUS_AUTH_COMMAND_CANCEL:
+ case DBUS_AUTH_COMMAND_BEGIN:
+ case DBUS_AUTH_COMMAND_UNKNOWN:
+ case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD:
+ case DBUS_AUTH_COMMAND_AGREE_UNIX_FD:
+ default:
+ return send_error (auth, "Unknown command");
+ }
+}
+
+static dbus_bool_t
+handle_client_state_waiting_for_reject (DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args)
+{
+ switch (command)
+ {
+ case DBUS_AUTH_COMMAND_REJECTED:
+ return process_rejected (auth, args);
+
+ case DBUS_AUTH_COMMAND_AUTH:
+ case DBUS_AUTH_COMMAND_CANCEL:
+ case DBUS_AUTH_COMMAND_DATA:
+ case DBUS_AUTH_COMMAND_BEGIN:
+ case DBUS_AUTH_COMMAND_OK:
+ case DBUS_AUTH_COMMAND_ERROR:
+ case DBUS_AUTH_COMMAND_UNKNOWN:
+ case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD:
+ case DBUS_AUTH_COMMAND_AGREE_UNIX_FD:
+ default:
+ goto_state (auth, &common_state_need_disconnect);
+ return TRUE;
+ }
+}
+
+static dbus_bool_t
+handle_client_state_waiting_for_agree_unix_fd(DBusAuth *auth,
+ DBusAuthCommand command,
+ const DBusString *args)
+{
+ switch (command)
+ {
+ case DBUS_AUTH_COMMAND_AGREE_UNIX_FD:
+ _dbus_assert(auth->unix_fd_possible);
+ auth->unix_fd_negotiated = TRUE;
+ _dbus_verbose("Successfully negotiated UNIX FD passing\n");
+ return send_begin (auth);
+
+ case DBUS_AUTH_COMMAND_ERROR:
+ _dbus_assert(auth->unix_fd_possible);
+ auth->unix_fd_negotiated = FALSE;
+ _dbus_verbose("Failed to negotiate UNIX FD passing\n");
+ return send_begin (auth);
+
+ case DBUS_AUTH_COMMAND_OK:
+ case DBUS_AUTH_COMMAND_DATA:
+ case DBUS_AUTH_COMMAND_REJECTED:
+ case DBUS_AUTH_COMMAND_AUTH:
+ case DBUS_AUTH_COMMAND_CANCEL:
+ case DBUS_AUTH_COMMAND_BEGIN:
+ case DBUS_AUTH_COMMAND_UNKNOWN:
+ case DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD:
+ default:
+ return send_error (auth, "Unknown command");
+ }
+}
+
+/**
+ * Mapping from command name to enum
+ */
+typedef struct {
+ const char *name; /**< Name of the command */
+ DBusAuthCommand command; /**< Corresponding enum */
+} DBusAuthCommandName;
+
+static const DBusAuthCommandName auth_command_names[] = {
+ { "AUTH", DBUS_AUTH_COMMAND_AUTH },
+ { "CANCEL", DBUS_AUTH_COMMAND_CANCEL },
+ { "DATA", DBUS_AUTH_COMMAND_DATA },
+ { "BEGIN", DBUS_AUTH_COMMAND_BEGIN },
+ { "REJECTED", DBUS_AUTH_COMMAND_REJECTED },
+ { "OK", DBUS_AUTH_COMMAND_OK },
+ { "ERROR", DBUS_AUTH_COMMAND_ERROR },
+ { "NEGOTIATE_UNIX_FD", DBUS_AUTH_COMMAND_NEGOTIATE_UNIX_FD },
+ { "AGREE_UNIX_FD", DBUS_AUTH_COMMAND_AGREE_UNIX_FD }
+};
+
+static DBusAuthCommand
+lookup_command_from_name (DBusString *command)
+{
+ int i;
+
+ for (i = 0; i < _DBUS_N_ELEMENTS (auth_command_names); i++)
+ {
+ if (_dbus_string_equal_c_str (command,
+ auth_command_names[i].name))
+ return auth_command_names[i].command;
+ }
+
+ return DBUS_AUTH_COMMAND_UNKNOWN;
+}
+
+static void
+goto_state (DBusAuth *auth,
+ const DBusAuthStateData *state)
+{
+ _dbus_verbose ("%s: going from state %s to state %s\n",
+ DBUS_AUTH_NAME (auth),
+ auth->state->name,
+ state->name);
+
+ auth->state = state;
+}
+
+/* returns whether to call it again right away */
+static dbus_bool_t
+process_command (DBusAuth *auth)
+{
+ DBusAuthCommand command;
+ DBusString line;
+ DBusString args;
+ int eol;
+ int i, j;
+ dbus_bool_t retval;
+
+ /* _dbus_verbose ("%s: trying process_command()\n"); */
+
+ retval = FALSE;
+
+ eol = 0;
+ if (!_dbus_string_find (&auth->incoming, 0, "\r\n", &eol))
+ return FALSE;
+
+ if (!_dbus_string_init (&line))
+ {
+ auth->needed_memory = TRUE;
+ return FALSE;
+ }
+
+ if (!_dbus_string_init (&args))
+ {
+ _dbus_string_free (&line);
+ auth->needed_memory = TRUE;
+ return FALSE;
+ }
+
+ if (!_dbus_string_copy_len (&auth->incoming, 0, eol, &line, 0))
+ goto out;
+
+ if (!_dbus_string_validate_ascii (&line, 0,
+ _dbus_string_get_length (&line)))
+ {
+ _dbus_verbose ("%s: Command contained non-ASCII chars or embedded nul\n",
+ DBUS_AUTH_NAME (auth));
+ if (!send_error (auth, "Command contained non-ASCII"))
+ goto out;
+ else
+ goto next_command;
+ }
+
+ _dbus_verbose ("%s: got command \"%s\"\n",
+ DBUS_AUTH_NAME (auth),
+ _dbus_string_get_const_data (&line));
+
+ _dbus_string_find_blank (&line, 0, &i);
+ _dbus_string_skip_blank (&line, i, &j);
+
+ if (j > i)
+ _dbus_string_delete (&line, i, j - i);
+
+ if (!_dbus_string_move (&line, i, &args, 0))
+ goto out;
+
+ /* FIXME 1.0 we should probably validate that only the allowed
+ * chars are in the command name
+ */
+
+ command = lookup_command_from_name (&line);
+ if (!(* auth->state->handler) (auth, command, &args))
+ goto out;
+
+ next_command:
+
+ /* 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 (&line);
+
+ 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 http://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
+ * for full details on what this object does.
+ *
+ * @returns the new object or #NULL if no memory
+ */
+DBusAuth*
+_dbus_auth_server_new (const DBusString *guid)
+{
+ DBusAuth *auth;
+ DBusAuthServer *server_auth;
+ DBusString guid_copy;
+
+ if (!_dbus_string_init (&guid_copy))
+ return NULL;
+
+ if (!_dbus_string_copy (guid, 0, &guid_copy, 0))
+ {
+ _dbus_string_free (&guid_copy);
+ return NULL;
+ }
+
+ auth = _dbus_auth_new (sizeof (DBusAuthServer));
+ if (auth == NULL)
+ {
+ _dbus_string_free (&guid_copy);
+ return NULL;
+ }
+
+ auth->side = auth_side_server;
+ auth->state = &server_state_waiting_for_auth;
+
+ server_auth = DBUS_AUTH_SERVER (auth);
+
+ server_auth->guid = guid_copy;
+
+ /* perhaps this should be per-mechanism with a lower
+ * max
+ */
+ server_auth->failures = 0;
+ server_auth->max_failures = 6;
+
+ return auth;
+}
+
+/**
+ * Creates a new auth conversation object for the client side.
+ * See http://dbus.freedesktop.org/doc/dbus-specification.html#auth-protocol
+ * 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;
+ DBusString guid_str;
+
+ if (!_dbus_string_init (&guid_str))
+ return NULL;
+
+ auth = _dbus_auth_new (sizeof (DBusAuthClient));
+ if (auth == NULL)
+ {
+ _dbus_string_free (&guid_str);
+ return NULL;
+ }
+
+ DBUS_AUTH_CLIENT (auth)->guid_from_server = guid_str;
+
+ auth->side = auth_side_client;
+ auth->state = &client_state_need_send_auth;
+
+ /* Start the auth conversation by sending AUTH for our default
+ * mechanism */
+ if (!send_auth (auth, &all_mechanisms[0]))
+ {
+ _dbus_auth_unref (auth);
+ return NULL;
+ }
+
+ return auth;
+}
+
+/**
+ * Increments the refcount of an auth object.
+ *
+ * @param auth the auth conversation
+ * @returns the auth conversation
+ */
+DBusAuth *
+_dbus_auth_ref (DBusAuth *auth)
+{
+ _dbus_assert (auth != NULL);
+
+ auth->refcount += 1;
+
+ return auth;
+}
+
+/**
+ * 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_string_free (& DBUS_AUTH_CLIENT (auth)->guid_from_server);
+ _dbus_list_clear (& DBUS_AUTH_CLIENT (auth)->mechs_to_try);
+ }
+ else
+ {
+ _dbus_assert (DBUS_AUTH_IS_SERVER (auth));
+
+ _dbus_string_free (& DBUS_AUTH_SERVER (auth)->guid);
+ }
+
+ 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);
+
+ dbus_free_string_array (auth->allowed_mechs);
+
+ _dbus_credentials_unref (auth->credentials);
+ _dbus_credentials_unref (auth->authorized_identity);
+ _dbus_credentials_unref (auth->desired_identity);
+
+ dbus_free (auth);
+ }
+}
+
+/**
+ * Sets an array of authentication mechanism names
+ * that we are willing to use.
+ *
+ * @param auth the auth conversation
+ * @param mechanisms #NULL-terminated array of mechanism names
+ * @returns #FALSE if no memory
+ */
+dbus_bool_t
+_dbus_auth_set_mechanisms (DBusAuth *auth,
+ const char **mechanisms)
+{
+ char **copy;
+
+ if (mechanisms != NULL)
+ {
+ copy = _dbus_dup_string_array (mechanisms);
+ if (copy == NULL)
+ return FALSE;
+ }
+ else
+ copy = NULL;
+
+ dbus_free_string_array (auth->allowed_mechs);
+
+ auth->allowed_mechs = copy;
+
+ return TRUE;
+}
+
+/**
+ * @param auth the auth conversation object
+ * @returns #TRUE if we're in a final state
+ */
+#define DBUS_AUTH_IN_END_STATE(auth) ((auth)->state->handler == NULL)
+
+/**
+ * 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)
+{
+ auth->needed_memory = FALSE;
+
+ /* Max amount we'll buffer up before deciding someone's on crack */
+#define MAX_BUFFER (16 * _DBUS_ONE_KILOBYTE)
+
+ do
+ {
+ if (DBUS_AUTH_IN_END_STATE (auth))
+ break;
+
+ if (_dbus_string_get_length (&auth->incoming) > MAX_BUFFER ||
+ _dbus_string_get_length (&auth->outgoing) > MAX_BUFFER)
+ {
+ goto_state (auth, &common_state_need_disconnect);
+ _dbus_verbose ("%s: Disconnecting due to excessive data buffered in auth phase\n",
+ DBUS_AUTH_NAME (auth));
+ break;
+ }
+ }
+ while (process_command (auth));
+
+ 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 if (auth->state == &common_state_need_disconnect)
+ return DBUS_AUTH_STATE_NEED_DISCONNECT;
+ else if (auth->state == &common_state_authenticated)
+ return DBUS_AUTH_STATE_AUTHENTICATED;
+ else return DBUS_AUTH_STATE_WAITING_FOR_INPUT;
+}
+
+/**
+ * Gets bytes that need to be sent to the peer we're conversing with.
+ * After writing some bytes, _dbus_auth_bytes_sent() must be called
+ * to notify the auth object that they were written.
+ *
+ * @param auth the auth conversation
+ * @param str return location for a ref to the buffer to send
+ * @returns #FALSE if nothing to send
+ */
+dbus_bool_t
+_dbus_auth_get_bytes_to_send (DBusAuth *auth,
+ const DBusString **str)
+{
+ _dbus_assert (auth != NULL);
+ _dbus_assert (str != NULL);
+
+ *str = NULL;
+
+ if (_dbus_string_get_length (&auth->outgoing) == 0)
+ return FALSE;
+
+ *str = &auth->outgoing;
+
+ return TRUE;
+}
+
+/**
+ * Notifies the auth conversation object that
+ * the given number of bytes of the outgoing buffer
+ * have been written out.
+ *
+ * @param auth the auth conversation
+ * @param bytes_sent number of bytes written out
+ */
+void
+_dbus_auth_bytes_sent (DBusAuth *auth,
+ int bytes_sent)
+{
+ _dbus_verbose ("%s: Sent %d bytes of: %s\n",
+ DBUS_AUTH_NAME (auth),
+ bytes_sent,
+ _dbus_string_get_const_data (&auth->outgoing));
+
+ _dbus_string_delete (&auth->outgoing,
+ 0, bytes_sent);
+}
+
+/**
+ * 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 buffer return location for buffer to append bytes to
+ */
+void
+_dbus_auth_get_buffer (DBusAuth *auth,
+ DBusString **buffer)
+{
+ _dbus_assert (auth != NULL);
+ _dbus_assert (!auth->buffer_outstanding);
+
+ *buffer = &auth->incoming;
+
+ auth->buffer_outstanding = TRUE;
+}
+
+/**
+ * Returns a buffer with new data read into it.
+ *
+ * @param auth the auth conversation
+ * @param buffer the buffer being returned
+ */
+void
+_dbus_auth_return_buffer (DBusAuth *auth,
+ DBusString *buffer)
+{
+ _dbus_assert (buffer == &auth->incoming);
+ _dbus_assert (auth->buffer_outstanding);
+
+ auth->buffer_outstanding = FALSE;
+}
+
+/**
+ * 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 return location for pointer to string of unused bytes
+ */
+void
+_dbus_auth_get_unused_bytes (DBusAuth *auth,
+ const DBusString **str)
+{
+ if (!DBUS_AUTH_IN_END_STATE (auth))
+ return;
+
+ *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);
+}
+
+/**
+ * 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->state != &common_state_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 where encoded data is appended
+ * @returns #TRUE if we had enough memory and successfully encoded
+ */
+dbus_bool_t
+_dbus_auth_encode_data (DBusAuth *auth,
+ const DBusString *plaintext,
+ DBusString *encoded)
+{
+ _dbus_assert (plaintext != encoded);
+
+ if (auth->state != &common_state_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,
+ _dbus_string_get_length (encoded));
+ }
+}
+
+/**
+ * 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->state != &common_state_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()).
+ *
+ * @todo 1.0? We need to be able to distinguish "out of memory" error
+ * from "the data is hosed" error.
+ *
+ * @param auth the auth conversation
+ * @param encoded the encoded data
+ * @param plaintext initialized string where decoded data is appended
+ * @returns #TRUE if we had enough memory and successfully decoded
+ */
+dbus_bool_t
+_dbus_auth_decode_data (DBusAuth *auth,
+ const DBusString *encoded,
+ DBusString *plaintext)
+{
+ _dbus_assert (plaintext != encoded);
+
+ if (auth->state != &common_state_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,
+ _dbus_string_get_length (plaintext));
+ }
+}
+
+/**
+ * Sets credentials received via reliable means from the operating
+ * system.
+ *
+ * @param auth the auth conversation
+ * @param credentials the credentials received
+ * @returns #FALSE on OOM
+ */
+dbus_bool_t
+_dbus_auth_set_credentials (DBusAuth *auth,
+ DBusCredentials *credentials)
+{
+ _dbus_credentials_clear (auth->credentials);
+ return _dbus_credentials_add_credentials (auth->credentials,
+ credentials);
+}
+
+/**
+ * Gets the identity we authorized the client as. Apps may have
+ * different policies as to what identities they allow.
+ *
+ * Returned credentials are not a copy and should not be modified
+ *
+ * @param auth the auth conversation
+ * @returns the credentials we've authorized BY REFERENCE do not modify
+ */
+DBusCredentials*
+_dbus_auth_get_identity (DBusAuth *auth)
+{
+ if (auth->state == &common_state_authenticated)
+ {
+ return auth->authorized_identity;
+ }
+ else
+ {
+ /* FIXME instead of this, keep an empty credential around that
+ * doesn't require allocation or something
+ */
+ /* return empty credentials */
+ _dbus_assert (_dbus_credentials_are_empty (auth->authorized_identity));
+ return auth->authorized_identity;
+ }
+}
+
+/**
+ * Gets the GUID from the server if we've authenticated; gets
+ * #NULL otherwise.
+ * @param auth the auth object
+ * @returns the GUID in ASCII hex format
+ */
+const char*
+_dbus_auth_get_guid_from_server (DBusAuth *auth)
+{
+ _dbus_assert (DBUS_AUTH_IS_CLIENT (auth));
+
+ if (auth->state == &common_state_authenticated)
+ return _dbus_string_get_const_data (& DBUS_AUTH_CLIENT (auth)->guid_from_server);
+ else
+ return NULL;
+}
+
+/**
+ * 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));
+}
+
+/**
+ * Sets whether unix fd passing is potentially on the transport and
+ * hence shall be negotiated.
+ *
+ * @param auth the auth conversation
+ * @param b TRUE when unix fd passing shall be negotiated, otherwise FALSE
+ */
+void
+_dbus_auth_set_unix_fd_possible(DBusAuth *auth, dbus_bool_t b)
+{
+ auth->unix_fd_possible = b;
+}
+
+/**
+ * Queries whether unix fd passing was successfully negotiated.
+ *
+ * @param auth the auth conversion
+ * @returns #TRUE when unix fd passing was negotiated.
+ */
+dbus_bool_t
+_dbus_auth_get_unix_fd_negotiated(DBusAuth *auth)
+{
+ return auth->unix_fd_negotiated;
+}
+
+/**
+ * Queries whether the given auth mechanism is supported.
+ *
+ * @param auth the auth mechanism to query for
+ * @returns #TRUE when auth mechanism is supported
+ */
+dbus_bool_t
+_dbus_auth_is_supported_mechanism (DBusString *name)
+{
+ _dbus_assert (name != NULL);
+
+ return find_mech (name, NULL) != NULL;
+}
+
+/**
+ * Return a human-readable string containing all supported auth mechanisms.
+ *
+ * @param string to hold the supported auth mechanisms
+ * @returns #FALSE on oom
+ */
+dbus_bool_t
+_dbus_auth_dump_supported_mechanisms (DBusString *buffer)
+{
+ unsigned int i;
+ _dbus_assert (buffer != NULL);
+
+ for (i = 0; all_mechanisms[i].mechanism != NULL; i++)
+ {
+ if (i > 0)
+ {
+ if (!_dbus_string_append (buffer, ", "))
+ return FALSE;
+ }
+ if (!_dbus_string_append (buffer, all_mechanisms[i].mechanism))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/** @} */
+
+/* tests in dbus-auth-util.c */