Commit 01af5ff4 authored by Havoc Pennington's avatar Havoc Pennington
Browse files

2003-01-04 Havoc Pennington <hp@pobox.com>

	* test/watch.c (error_handler): make it safe if the error handler
	is called multiple times (if we s/error handler/disconnect
	handler/ we should just guarantee it's called only once)

	* dbus/dbus-transport.c (_dbus_transport_disconnect): call the
	error handler on disconnect (it's quite possible we should
	just change the error handler to a "disconnect handler," I'm
	not sure we have any other meaningful errors)

	* configure.in: check for getpwnam_r

	* dbus/dbus-transport.c, dbus/dbus-transport-unix.c,
	dbus/dbus-auth.c: add credentials support, add EXTERNAL auth
	mechanism as in SASL spec, using socket credentials

	* dbus/dbus-sysdeps.c (_dbus_read_credentials_unix_socket): new function
	(_dbus_send_credentials_unix_socket): new function

	* dbus/dbus-sysdeps.c (_dbus_accept_unix_socket): rename just
	dbus_accept()
	(_dbus_write): only check errno if <0 returned
	(_dbus_write_two): ditto
parent 1ed128b5
2003-01-04 Havoc Pennington <hp@pobox.com>
* test/watch.c (error_handler): make it safe if the error handler
is called multiple times (if we s/error handler/disconnect
handler/ we should just guarantee it's called only once)
* dbus/dbus-transport.c (_dbus_transport_disconnect): call the
error handler on disconnect (it's quite possible we should
just change the error handler to a "disconnect handler," I'm
not sure we have any other meaningful errors)
* configure.in: check for getpwnam_r
* dbus/dbus-transport.c, dbus/dbus-transport-unix.c,
dbus/dbus-auth.c: add credentials support, add EXTERNAL auth
mechanism as in SASL spec, using socket credentials
* dbus/dbus-sysdeps.c (_dbus_read_credentials_unix_socket): new function
(_dbus_send_credentials_unix_socket): new function
* dbus/dbus-sysdeps.c (_dbus_accept_unix_socket): rename just
dbus_accept()
(_dbus_write): only check errno if <0 returned
(_dbus_write_two): ditto
2003-01-02 Anders Carlsson <andersca@codefactory.se>
* dbus/dbus-marshal.c: (_dbus_marshal_utf8_string),
......
......@@ -101,7 +101,7 @@ AC_CHECK_SIZEOF(__int64)
## byte order
AC_C_BIGENDIAN
AC_CHECK_FUNCS(vsnprintf vasprintf)
AC_CHECK_FUNCS(vsnprintf vasprintf getpwnam_r)
dnl check for writev header and writev function so we're
dnl good to go if HAVE_WRITEV gets defined.
......
......@@ -64,6 +64,12 @@ typedef struct
DBusProcessAuthCommandFunction func;
} DBusAuthCommandHandler;
/**
* 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.
......@@ -97,6 +103,7 @@ typedef struct
DBusAuthEncodeFunction server_encode_func;
DBusAuthDecodeFunction server_decode_func;
DBusAuthShutdownFunction server_shutdown_func;
DBusInitialResponseFunction client_initial_response_func;
DBusAuthDataFunction client_data_func;
DBusAuthEncodeFunction client_encode_func;
DBusAuthDecodeFunction client_decode_func;
......@@ -116,6 +123,14 @@ struct DBusAuth
const DBusAuthCommandHandler *handlers; /**< Handlers for commands */
const DBusAuthMechanismHandler *mech; /**< Current auth mechanism */
DBusString identity; /**< Current identity we're authorizing
* as.
*/
DBusCredentials credentials; /**< Credentials, fields may be -1 */
DBusCredentials authorized_identity; /**< Credentials that are authorized */
unsigned int needed_memory : 1; /**< We needed memory to continue since last
* successful getting something done
......@@ -125,6 +140,7 @@ struct DBusAuth
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 */
unsigned int already_asked_for_initial_response : 1; /**< Already sent a blank challenge to get an initial response */
};
typedef struct
......@@ -228,6 +244,14 @@ _dbus_auth_new (int size)
auth->refcount = 1;
auth->credentials.pid = -1;
auth->credentials.uid = -1;
auth->credentials.gid = -1;
auth->authorized_identity.pid = -1;
auth->authorized_identity.uid = -1;
auth->authorized_identity.gid = -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
......@@ -244,6 +268,14 @@ _dbus_auth_new (int size)
if (!_dbus_string_init (&auth->outgoing, _DBUS_INT_MAX))
{
_dbus_string_free (&auth->incoming);
dbus_free (auth);
return NULL;
}
if (!_dbus_string_init (&auth->identity, _DBUS_INT_MAX))
{
_dbus_string_free (&auth->incoming);
_dbus_string_free (&auth->outgoing);
dbus_free (auth);
return NULL;
......@@ -278,6 +310,11 @@ shutdown_mech (DBusAuth *auth)
/* Cancel any auth */
auth->authenticated_pending_begin = FALSE;
auth->authenticated = FALSE;
auth->already_asked_for_initial_response = FALSE;
_dbus_string_set_length (&auth->identity, 0);
auth->authorized_identity.pid = -1;
auth->authorized_identity.uid = -1;
auth->authorized_identity.gid = -1;
if (auth->mech != NULL)
{
......@@ -353,6 +390,163 @@ handle_decode_stupid_test_mech (DBusAuth *auth,
return TRUE;
}
static dbus_bool_t
do_rejection (DBusAuth *auth)
{
if (_dbus_string_append (&auth->outgoing,
"REJECTED\r\n"))
{
shutdown_mech (auth);
_dbus_verbose ("rejected client auth\n");
return TRUE;
}
else
return FALSE;
}
static dbus_bool_t
handle_server_data_external_mech (DBusAuth *auth,
const DBusString *data)
{
DBusCredentials desired_identity;
if (auth->credentials.uid < 0)
{
_dbus_verbose ("no credentials, mechanism EXTERNAL can't authenticate\n");
return do_rejection (auth);
}
if (_dbus_string_get_length (data) > 0)
{
if (_dbus_string_get_length (&auth->identity) > 0)
{
/* Tried to send two auth identities, wtf */
return do_rejection (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 (_dbus_string_append (&auth->outgoing,
"DATA\r\n"))
{
_dbus_verbose ("sending empty challenge asking client for auth identity\n");
auth->already_asked_for_initial_response = TRUE;
return TRUE;
}
else
return FALSE;
}
desired_identity.pid = -1;
desired_identity.uid = -1;
desired_identity.gid = -1;
/* 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)
{
desired_identity.uid = auth->credentials.uid;
}
else
{
if (!_dbus_credentials_from_uid_string (&auth->identity,
&desired_identity))
return do_rejection (auth);
}
if (desired_identity.uid < 0)
{
_dbus_verbose ("desired UID %d is no good\n", desired_identity.uid);
return do_rejection (auth);
}
if (_dbus_credentials_match (&auth->credentials,
&desired_identity))
{
/* client has authenticated */
_dbus_verbose ("authenticated client with UID %d matching socket credentials UID %d\n",
desired_identity.uid,
auth->credentials.uid);
if (!_dbus_string_append (&auth->outgoing,
"OK\r\n"))
return FALSE;
auth->authorized_identity.uid = desired_identity.uid;
auth->authenticated_pending_begin = TRUE;
return TRUE;
}
else
{
return do_rejection (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, _DBUS_INT_MAX))
return FALSE;
if (!_dbus_string_append_our_uid (&plaintext))
goto failed;
if (!_dbus_string_base64_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)
{
}
/* Put mechanisms here in order of preference.
* What I eventually want to have is:
*
......@@ -364,11 +558,21 @@ handle_decode_stupid_test_mech (DBusAuth *auth,
*/
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 },
/* Obviously this has to die for production use */
{ "DBUS_STUPID_TEST_MECH",
handle_server_data_stupid_test_mech,
handle_encode_stupid_test_mech,
handle_decode_stupid_test_mech,
handle_server_shutdown_stupid_test_mech,
NULL,
handle_client_data_stupid_test_mech,
handle_encode_stupid_test_mech,
handle_decode_stupid_test_mech,
......@@ -496,8 +700,9 @@ process_auth (DBusAuth *auth,
auth->mech = find_mech (&mech);
if (auth->mech != NULL)
{
_dbus_verbose ("Trying mechanism %s\n",
auth->mech->mechanism);
_dbus_verbose ("Trying mechanism %s with initial response of %d bytes\n",
auth->mech->mechanism,
_dbus_string_get_length (&decoded_response));
if (!(* auth->mech->server_data_func) (auth,
&decoded_response))
......@@ -702,15 +907,30 @@ client_try_next_mechanism (DBusAuth *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"))
{
......@@ -1327,4 +1547,42 @@ _dbus_auth_decode_data (DBusAuth *auth,
}
}
/**
* Sets credentials received via reliable means from the operating
* system.
*
* @param auth the auth conversation
* @param credentials the credentials received
*/
void
_dbus_auth_set_credentials (DBusAuth *auth,
const DBusCredentials *credentials)
{
auth->credentials = *credentials;
}
/**
* Gets the identity we authorized the client as. Apps may have
* different policies as to what identities they allow.
*
* @param auth the auth conversation
* @param credentials the credentials we've authorized
*/
void
_dbus_auth_get_identity (DBusAuth *auth,
DBusCredentials *credentials)
{
if (auth->authenticated)
{
*credentials = auth->authorized_identity;
}
else
{
credentials->pid = -1;
credentials->uid = -1;
credentials->gid = -1;
}
}
/** @} */
......@@ -26,6 +26,7 @@
#include <dbus/dbus-macros.h>
#include <dbus/dbus-errors.h>
#include <dbus/dbus-string.h>
#include <dbus/dbus-sysdeps.h>
DBUS_BEGIN_DECLS;
......@@ -43,26 +44,30 @@ typedef enum
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,
const DBusString **str);
void _dbus_auth_bytes_sent (DBusAuth *auth,
int bytes_sent);
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);
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,
const DBusString **str);
void _dbus_auth_bytes_sent (DBusAuth *auth,
int bytes_sent);
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);
void _dbus_auth_set_credentials (DBusAuth *auth,
const DBusCredentials *credentials);
void _dbus_auth_get_identity (DBusAuth *auth,
DBusCredentials *credentials);
DBUS_END_DECLS;
......
......@@ -128,7 +128,7 @@ unix_handle_watch (DBusServer *server,
listen_fd = dbus_watch_get_fd (watch);
client_fd = _dbus_accept_unix_socket (listen_fd);
client_fd = _dbus_accept (listen_fd);
if (client_fd < 0)
{
......
......@@ -33,6 +33,7 @@
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pwd.h>
#ifdef HAVE_WRITEV
#include <sys/uio.h>
#endif
......@@ -151,7 +152,7 @@ _dbus_write (int fd,
bytes_written = write (fd, data, len);
if (errno == EINTR)
if (bytes_written < 0 && errno == EINTR)
goto again;
#if 0
......@@ -226,7 +227,7 @@ _dbus_write_two (int fd,
vectors,
data2 ? 2 : 1);
if (errno == EINTR)
if (bytes_written < 0 && errno == EINTR)
goto again;
return bytes_written;
......@@ -367,17 +368,184 @@ _dbus_listen_unix_socket (const char *path,
return listen_fd;
}
/* try to read a single byte and return #TRUE if we read it
* and it's equal to nul.
*/
static dbus_bool_t
read_credentials_byte (int client_fd,
DBusResultCode *result)
{
char buf[1];
int bytes_read;
again:
bytes_read = read (client_fd, buf, 1);
if (bytes_read < 0)
{
if (errno == EINTR)
goto again;
else
{
dbus_set_result (result, _dbus_result_from_errno (errno));
_dbus_verbose ("Failed to read credentials byte: %s\n",
_dbus_strerror (errno));
return FALSE;
}
}
else if (bytes_read == 0)
{
dbus_set_result (result, DBUS_RESULT_IO_ERROR);
_dbus_verbose ("EOF reading credentials byte\n");
return FALSE;
}
else
{
_dbus_assert (bytes_read == 1);
if (buf[0] != '\0')
{
dbus_set_result (result, DBUS_RESULT_FAILED);
_dbus_verbose ("Credentials byte was not nul\n");
return FALSE;
}
_dbus_verbose ("read credentials byte\n");
return TRUE;
}
}
static dbus_bool_t
write_credentials_byte (int server_fd,
DBusResultCode *result)
{
int bytes_written;
char buf[1] = { '\0' };
again:
bytes_written = write (server_fd, buf, 1);
if (bytes_written < 0 && errno == EINTR)
goto again;
if (bytes_written < 0)
{
dbus_set_result (result, _dbus_result_from_errno (errno));
_dbus_verbose ("Failed to write credentials byte: %s\n",
_dbus_strerror (errno));
return FALSE;
}
else if (bytes_written == 0)
{
dbus_set_result (result, DBUS_RESULT_IO_ERROR);
_dbus_verbose ("wrote zero bytes writing credentials byte\n");
return FALSE;
}
else
{
_dbus_assert (bytes_written == 1);
_dbus_verbose ("wrote credentials byte\n");
return TRUE;
}
}
/**
* Reads a single byte which must be nul (an error occurs otherwise),
* and reads unix credentials if available. Fills in pid/uid/gid with
* -1 if no credentials are available. Return value indicates whether
* a byte was read, not whether we got valid credentials. On some
* systems, such as Linux, reading/writing the byte isn't actually
* required, but we do it anyway just to avoid multiple codepaths.
*
* Fails if no byte is available, so you must select() first.
*
* The point of the byte is that on some systems we have to
* use sendmsg()/recvmsg() to transmit credentials.
*
* @param client_fd the client file descriptor
* @param credentials struct to fill with credentials of client
* @param result location to store result code
* @returns #TRUE on success
*/
dbus_bool_t
_dbus_read_credentials_unix_socket (int client_fd,
DBusCredentials *credentials,
DBusResultCode *result)
{
credentials->pid = -1;
credentials->uid = -1;
credentials->gid = -1;
#ifdef SO_PEERCRED
if (read_credentials_byte (client_fd, result))
{
struct ucred cr;
int cr_len = sizeof (cr);
if (getsockopt (client_fd, SOL_SOCKET, SO_PEERCRED, &cr, &cr_len) == 0 &&
cr_len == sizeof (cr))
{
credentials->pid = cr.pid;
credentials->uid = cr.uid;
credentials->gid = cr.gid;
_dbus_verbose ("Got credentials pid %d uid %d gid %d\n",
credentials->pid,
credentials->uid,
credentials->gid);
}
else
{
_dbus_verbose ("Failed to getsockopt() credentials, returned len %d/%d: %s\n",
cr_len, (int) sizeof (cr), _dbus_strerror (errno));
}
return TRUE;
}
else
return FALSE;
#else /* !SO_PEERCRED */
_dbus_verbose ("Socket credentials not supported on this OS\n");
return TRUE;
#endif
}
/**
* Sends a single nul byte with our UNIX credentials as ancillary