| /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| /* dbus-auth.c Authentication |
| * |
| * Copyright (C) 2002, 2003, 2004 Red Hat Inc. |
| * |
| * 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 "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 manage 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 |
| }; |
| 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; |
| DBusString tmp2; |
| dbus_bool_t retval; |
| DBusError error; |
| |
| retval = FALSE; |
| |
| _dbus_string_set_length (&auth->challenge, 0); |
| |
| if (_dbus_string_get_length (data) > 0) |
| { |
| if (_dbus_string_get_length (&auth->identity) > 0) |
| { |
| /* Tried to send two auth identities, wtf */ |
| _dbus_verbose ("%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_verbose ("%s: Did not get a valid username from client\n", |
| DBUS_AUTH_NAME (auth)); |
| return send_rejected (auth); |
| } |
| |
| if (!_dbus_string_init (&tmp)) |
| return FALSE; |
| |
| if (!_dbus_string_init (&tmp2)) |
| { |
| _dbus_string_free (&tmp); |
| return FALSE; |
| } |
| |
| /* 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) |
| { |
| dbus_error_init (&error); |
| 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); |
| |
| dbus_error_init (&error); |
| auth->cookie_id = _dbus_keyring_get_best_key (auth->keyring, &error); |
| if (auth->cookie_id < 0) |
| { |
| _DBUS_ASSERT_ERROR_IS_SET (&error); |
| _dbus_verbose ("%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)) |
| 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); |
| |
| 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 from the socket credentials if it's there |
| */ |
| 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; |
| DBusString context; |
| DBusString cookie_id_str; |
| DBusString server_challenge; |
| DBusString client_challenge; |
| DBusString correct_hash; |
| DBusString tmp; |
| int i, j; |
| long val; |
| |
| retval = FALSE; |
| |
| 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) |
| { |
| DBusError error; |
| |
| dbus_error_init (&error); |
| 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)) |
| 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 |
| { |
| if (!_dbus_credentials_add_from_user (auth->desired_identity, |
| &auth->identity)) |
| { |
| _dbus_verbose ("%s: could not get credentials from uid string\n", |
| DBUS_AUTH_NAME (auth)); |
| 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 process ID from the socket credentials |
| */ |
| if (!_dbus_credentials_add_credential (auth->authorized_identity, |
| DBUS_CREDENTIAL_UNIX_PROCESS_ID, |
| auth->credentials)) |
| return FALSE; |
| |
| /* also copy audit data from the socket credentials |
| */ |
| if (!_dbus_credentials_add_credential (auth->authorized_identity, |
| DBUS_CREDENTIAL_ADT_AUDIT_DATA_ID, |
| 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)); |
| |
| { |
| DBusString plaintext; |
| DBusString encoded; |
| _dbus_string_init_const (&plaintext, "D-Bus " DBUS_VERSION_STRING); |
| _dbus_string_init (&encoded); |
| _dbus_string_hex_encode (&plaintext, 0, |
| &encoded, |
| 0); |
| _dbus_verbose ("%s: try '%s'\n", |
| DBUS_AUTH_NAME (auth), _dbus_string_get_const_data (&encoded)); |
| } |
| 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 from the socket credentials |
| */ |
| 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; |
| |
| 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; |
| |
| 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) |
| return send_negotiate_unix_fd(auth); |
| |
| _dbus_verbose("Not negotiating unix fd passing, since not possible\n"); |
| return send_begin (auth); |
| } |
| |
| 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("Sucessfully 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 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 (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 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; |
| 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 |
| * @param bytes_read number of new bytes added |
| */ |
| void |
| _dbus_auth_return_buffer (DBusAuth *auth, |
| DBusString *buffer, |
| int bytes_read) |
| { |
| _dbus_assert (buffer == &auth->incoming); |
| _dbus_assert (auth->buffer_outstanding); |
| |
| auth->buffer_outstanding = FALSE; |
| } |
| |
| /** |
| * 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 sucessfully 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; |
| } |
| |
| /** @} */ |
| |
| /* tests in dbus-auth-util.c */ |