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