summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog25
-rw-r--r--configure.in2
-rw-r--r--dbus/dbus-auth.c268
-rw-r--r--dbus/dbus-auth.h43
-rw-r--r--dbus/dbus-server-unix.c2
-rw-r--r--dbus/dbus-sysdeps.c350
-rw-r--r--dbus/dbus-sysdeps.h26
-rw-r--r--dbus/dbus-transport-protected.h5
-rw-r--r--dbus/dbus-transport-unix.c62
-rw-r--r--dbus/dbus-transport.c49
-rw-r--r--doc/dbus-sasl-profile.txt23
-rw-r--r--test/watch.c3
12 files changed, 817 insertions, 41 deletions
diff --git a/ChangeLog b/ChangeLog
index 94f8beb1..3008d92c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+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),
diff --git a/configure.in b/configure.in
index a2b81699..9435c9de 100644
--- a/configure.in
+++ b/configure.in
@@ -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.
diff --git a/dbus/dbus-auth.c b/dbus/dbus-auth.c
index d39a7770..032e49a8 100644
--- a/dbus/dbus-auth.c
+++ b/dbus/dbus-auth.c
@@ -65,6 +65,12 @@ typedef struct
} 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;
+ }
+}
+
+
/** @} */
diff --git a/dbus/dbus-auth.h b/dbus/dbus-auth.h
index 824426af..7346f99b 100644
--- a/dbus/dbus-auth.h
+++ b/dbus/dbus-auth.h
@@ -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;
diff --git a/dbus/dbus-server-unix.c b/dbus/dbus-server-unix.c
index 66494bd4..c9a494e7 100644
--- a/dbus/dbus-server-unix.c
+++ b/dbus/dbus-server-unix.c
@@ -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)
{
diff --git a/dbus/dbus-sysdeps.c b/dbus/dbus-sysdeps.c
index fed88144..f85a4a95 100644
--- a/dbus/dbus-sysdeps.c
+++ b/dbus/dbus-sysdeps.c
@@ -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
+ * data. Returns #TRUE if the data was successfully written. On
+ * systems that don't support sending credentials, just writes a byte,
+ * doesn't send any 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 can be written, 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 server_fd file descriptor for connection to server
+ * @param result return location for error code
+ * @returns #TRUE if the byte was sent
+ */
+dbus_bool_t
+_dbus_send_credentials_unix_socket (int server_fd,
+ DBusResultCode *result)
+{
+ if (write_credentials_byte (server_fd, result))
+ return TRUE;
+ else
+ return FALSE;
+}
+
/**
- * Accepts a connection on a listening UNIX socket.
- * Specific to UNIX domain sockets because we might
- * add extra args to this function later to get client
- * credentials. Handles EINTR for you.
+ * Accepts a connection on a listening socket.
+ * Handles EINTR for you.
*
* @param listen_fd the listen file descriptor
* @returns the connection fd of the client, or -1 on error
*/
int
-_dbus_accept_unix_socket (int listen_fd)
+_dbus_accept (int listen_fd)
{
int client_fd;
@@ -389,7 +557,7 @@ _dbus_accept_unix_socket (int listen_fd)
if (errno == EINTR)
goto retry;
}
-
+
return client_fd;
}
@@ -559,4 +727,168 @@ _dbus_string_parse_double (const DBusString *str,
return TRUE;
}
-/** @} end of DBusString */
+/**
+ * Gets the credentials corresponding to the given username.
+ *
+ * @param username the username
+ * @param credentials credentials to fill in
+ * @returns #TRUE if the username existed and we got some credentials
+ */
+dbus_bool_t
+_dbus_credentials_from_username (const DBusString *username,
+ DBusCredentials *credentials)
+{
+ const char *username_c_str;
+
+ credentials->pid = -1;
+ credentials->uid = -1;
+ credentials->gid = -1;
+
+ _dbus_string_get_const_data (username, &username_c_str);
+
+#ifdef HAVE_GETPWNAM_R
+ {
+ struct passwd *p;
+ int result;
+ char buf[1024];
+ struct passwd p_str;
+
+ p = NULL;
+ result = getpwnam_r (username_c_str, &p_str, buf, sizeof (buf),
+ &p);
+
+ if (result == 0 && p == &p_str)
+ {
+ credentials->uid = p->pw_uid;
+ credentials->gid = p->pw_gid;
+
+ _dbus_verbose ("Username %s has uid %d gid %d\n",
+ username_c_str, credentials->uid, credentials->gid);
+ return TRUE;
+ }
+ else
+ {
+ _dbus_verbose ("User %s unknown\n", username_c_str);
+ return FALSE;
+ }
+ }
+#else /* ! HAVE_GETPWNAM_R */
+ {
+ /* I guess we're screwed on thread safety here */
+ struct passwd *p;
+
+ p = getpwnam (username_c_str);
+
+ if (p != NULL)
+ {
+ credentials->uid = p->pw_uid;
+ credentials->gid = p->pw_gid;
+
+ _dbus_verbose ("Username %s has uid %d gid %d\n",
+ username_c_str, credentials->uid, credentials->gid);
+ return TRUE;
+ }
+ else
+ {
+ _dbus_verbose ("User %s unknown\n", username_c_str);
+ return FALSE;
+ }
+ }
+#endif
+}
+
+/**
+ * Gets credentials from a UID string. (Parses a string to a UID
+ * and converts to a DBusCredentials.)
+ *
+ * @param uid_str the UID in string form
+ * @param credentials credentials to fill in
+ * @returns #TRUE if successfully filled in some credentials
+ */
+dbus_bool_t
+_dbus_credentials_from_uid_string (const DBusString *uid_str,
+ DBusCredentials *credentials)
+{
+ int end;
+ long uid;
+
+ credentials->pid = -1;
+ credentials->uid = -1;
+ credentials->gid = -1;
+
+ if (_dbus_string_get_length (uid_str) == 0)
+ {
+ _dbus_verbose ("UID string was zero length\n");
+ return FALSE;
+ }
+
+ uid = -1;
+ end = 0;
+ if (!_dbus_string_parse_int (uid_str, 0, &uid,
+ &end))
+ {
+ _dbus_verbose ("could not parse string as a UID\n");
+ return FALSE;
+ }
+
+ if (end != _dbus_string_get_length (uid_str))
+ {
+ _dbus_verbose ("string contained trailing stuff after UID\n");
+ return FALSE;
+ }
+
+ credentials->uid = uid;
+
+ return TRUE;
+}
+
+/**
+ * Gets the credentials of the current process.
+ *
+ * @param credentials credentials to fill in.
+ */
+void
+_dbus_credentials_from_current_process (DBusCredentials *credentials)
+{
+ credentials->pid = getpid ();
+ credentials->uid = getuid ();
+ credentials->gid = getgid ();
+}
+
+/**
+ * Checks whether the provided_credentials are allowed to log in
+ * as the expected_credentials.
+ *
+ * @param expected_credentials credentials we're trying to log in as
+ * @param provided_credentials credentials we have
+ * @returns #TRUE if we can log in
+ */
+dbus_bool_t
+_dbus_credentials_match (const DBusCredentials *expected_credentials,
+ const DBusCredentials *provided_credentials)
+{
+ if (provided_credentials->uid < 0)
+ return FALSE;
+ else if (expected_credentials->uid < 0)
+ return FALSE;
+ else if (provided_credentials->uid == 0)
+ return TRUE;
+ else if (provided_credentials->uid == expected_credentials->uid)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+/**
+ * Appends the uid of the current process to the given string.
+ *
+ * @param str the string to append to
+ * @returns #TRUE on success
+ */
+dbus_bool_t
+_dbus_string_append_our_uid (DBusString *str)
+{
+ return _dbus_string_append_int (str, getuid ());
+}
+
+/** @} end of sysdeps */
diff --git a/dbus/dbus-sysdeps.h b/dbus/dbus-sysdeps.h
index 17d96b3d..73482f06 100644
--- a/dbus/dbus-sysdeps.h
+++ b/dbus/dbus-sysdeps.h
@@ -65,12 +65,36 @@ int _dbus_write_two (int fd,
int start2,
int len2);
+typedef struct
+{
+ /* -1 if not available */
+ int pid;
+ int uid;
+ int gid;
+} DBusCredentials;
+
int _dbus_connect_unix_socket (const char *path,
DBusResultCode *result);
int _dbus_listen_unix_socket (const char *path,
DBusResultCode *result);
-int _dbus_accept_unix_socket (int listen_fd);
+int _dbus_accept (int listen_fd);
+
+dbus_bool_t _dbus_read_credentials_unix_socket (int client_fd,
+ DBusCredentials *credentials,
+ DBusResultCode *result);
+dbus_bool_t _dbus_send_credentials_unix_socket (int server_fd,
+ DBusResultCode *result);
+
+
+dbus_bool_t _dbus_credentials_from_username (const DBusString *username,
+ DBusCredentials *credentials);
+dbus_bool_t _dbus_credentials_from_uid_string (const DBusString *uid_str,
+ DBusCredentials *credentials);
+void _dbus_credentials_from_current_process (DBusCredentials *credentials);
+dbus_bool_t _dbus_credentials_match (const DBusCredentials *expected_credentials,
+ const DBusCredentials *provided_credentials);
+dbus_bool_t _dbus_string_append_our_uid (DBusString *str);
DBUS_END_DECLS;
diff --git a/dbus/dbus-transport-protected.h b/dbus/dbus-transport-protected.h
index 5105165a..b9bbb40d 100644
--- a/dbus/dbus-transport-protected.h
+++ b/dbus/dbus-transport-protected.h
@@ -77,9 +77,14 @@ struct DBusTransport
DBusAuth *auth; /**< Authentication conversation */
+ DBusCredentials credentials; /**< Credentials of other end */
+
unsigned int disconnected : 1; /**< #TRUE if we are disconnected. */
unsigned int authenticated : 1; /**< Cache of auth state; use _dbus_transport_get_is_authenticated() to query value */
unsigned int messages_need_sending : 1; /**< #TRUE if we need to write messages out */
+ unsigned int send_credentials_pending : 1; /**< #TRUE if we need to send credentials */
+ unsigned int receive_credentials_pending : 1; /**< #TRUE if we need to receive credentials */
+ unsigned int is_server : 1; /**< #TRUE if on the server side */
};
dbus_bool_t _dbus_transport_init_base (DBusTransport *transport,
diff --git a/dbus/dbus-transport-unix.c b/dbus/dbus-transport-unix.c
index dd0c6833..ba1528c4 100644
--- a/dbus/dbus-transport-unix.c
+++ b/dbus/dbus-transport-unix.c
@@ -123,7 +123,8 @@ check_write_watch (DBusTransport *transport)
if (_dbus_transport_get_is_authenticated (transport))
need_write_watch = transport->messages_need_sending;
else
- need_write_watch = _dbus_auth_do_work (transport->auth) == DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
+ need_write_watch = transport->send_credentials_pending ||
+ _dbus_auth_do_work (transport->auth) == DBUS_AUTH_STATE_HAVE_BYTES_TO_SEND;
if (transport->disconnected)
need_write_watch = FALSE;
@@ -391,15 +392,70 @@ recover_unused_bytes (DBusTransport *transport)
}
static void
+exchange_credentials (DBusTransport *transport,
+ dbus_bool_t do_reading,
+ dbus_bool_t do_writing)
+{
+ DBusTransportUnix *unix_transport = (DBusTransportUnix*) transport;
+
+ if (do_writing && transport->send_credentials_pending)
+ {
+ if (_dbus_send_credentials_unix_socket (unix_transport->fd,
+ NULL))
+ {
+ transport->send_credentials_pending = FALSE;
+ }
+ else
+ {
+ _dbus_verbose ("Failed to write credentials\n");
+ do_io_error (transport);
+ }
+ }
+
+ if (do_reading && transport->receive_credentials_pending)
+ {
+ if (_dbus_read_credentials_unix_socket (unix_transport->fd,
+ &transport->credentials,
+ NULL))
+ {
+ transport->receive_credentials_pending = FALSE;
+ }
+ else
+ {
+ _dbus_verbose ("Failed to read credentials\n");
+ do_io_error (transport);
+ }
+ }
+
+ if (!(transport->send_credentials_pending ||
+ transport->receive_credentials_pending))
+ {
+ _dbus_auth_set_credentials (transport->auth,
+ &transport->credentials);
+ }
+}
+
+static void
do_authentication (DBusTransport *transport,
dbus_bool_t do_reading,
dbus_bool_t do_writing)
-{
+{
_dbus_transport_ref (transport);
while (!_dbus_transport_get_is_authenticated (transport) &&
_dbus_transport_get_is_connected (transport))
{
+ exchange_credentials (transport, do_reading, do_writing);
+
+ if (transport->send_credentials_pending ||
+ transport->receive_credentials_pending)
+ {
+ _dbus_verbose ("send_credentials_pending = %d receive_credentials_pending = %d\n",
+ transport->send_credentials_pending,
+ transport->receive_credentials_pending);
+ goto out;
+ }
+
switch (_dbus_auth_do_work (transport->auth))
{
case DBUS_AUTH_STATE_WAITING_FOR_INPUT:
@@ -963,7 +1019,7 @@ _dbus_transport_new_for_domain_socket (const char *path,
close (fd);
fd = -1;
}
-
+
return transport;
}
diff --git a/dbus/dbus-transport.c b/dbus/dbus-transport.c
index 085b0224..110153dd 100644
--- a/dbus/dbus-transport.c
+++ b/dbus/dbus-transport.c
@@ -105,6 +105,13 @@ _dbus_transport_init_base (DBusTransport *transport,
transport->authenticated = FALSE;
transport->messages_need_sending = FALSE;
transport->disconnected = FALSE;
+ transport->send_credentials_pending = !server;
+ transport->receive_credentials_pending = server;
+ transport->is_server = server;
+
+ transport->credentials.pid = -1;
+ transport->credentials.uid = -1;
+ transport->credentials.gid = -1;
return TRUE;
}
@@ -205,8 +212,12 @@ _dbus_transport_disconnect (DBusTransport *transport)
DBUS_TRANSPORT_HOLD_REF (transport);
(* transport->vtable->disconnect) (transport);
-
+
transport->disconnected = TRUE;
+
+ _dbus_connection_transport_error (transport->connection,
+ DBUS_RESULT_DISCONNECTED);
+
DBUS_TRANSPORT_RELEASE_REF (transport);
}
@@ -238,9 +249,45 @@ _dbus_transport_get_is_authenticated (DBusTransport *transport)
return TRUE;
else
{
+ if (transport->disconnected)
+ return FALSE;
+
transport->authenticated =
+ (!(transport->send_credentials_pending ||
+ transport->receive_credentials_pending)) &&
_dbus_auth_do_work (transport->auth) == DBUS_AUTH_STATE_AUTHENTICATED;
+ /* If we've authenticated as some identity, check that the auth
+ * identity is the same as our own identity. In the future, we
+ * may have API allowing applications to specify how this is
+ * done, for example they may allow connection as any identity,
+ * but then impose restrictions on certain identities.
+ * Or they may give certain identities extra privileges.
+ */
+
+ if (transport->authenticated && transport->is_server)
+ {
+ DBusCredentials auth_identity;
+ DBusCredentials our_identity;
+
+ _dbus_credentials_from_current_process (&our_identity);
+ _dbus_auth_get_identity (transport->auth, &auth_identity);
+
+ if (!_dbus_credentials_match (&our_identity,
+ &auth_identity))
+ {
+ _dbus_verbose ("Client authorized as UID %d but our UID is %d, disconnecting\n",
+ auth_identity.uid, our_identity.uid);
+ _dbus_transport_disconnect (transport);
+ return FALSE;
+ }
+ else
+ {
+ _dbus_verbose ("Client authorized as UID %d matching our UID %d\n",
+ auth_identity.uid, our_identity.uid);
+ }
+ }
+
return transport->authenticated;
}
}
diff --git a/doc/dbus-sasl-profile.txt b/doc/dbus-sasl-profile.txt
index 44c756ad..c2e8cd77 100644
--- a/doc/dbus-sasl-profile.txt
+++ b/doc/dbus-sasl-profile.txt
@@ -19,7 +19,7 @@ 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
+Protocol Overview
===
The protocol is a line-based protocol, where each line ends with
@@ -52,6 +52,27 @@ From server to client are as follows:
ERROR
+
+Special credentials-passing nul byte
+===
+
+Immediately after connecting to the server, the client must send a
+single nul byte. This byte may be accompanied by credentials
+information on some operating systems that use sendmsg() with
+SCM_CREDS or SCM_CREDENTIALS to pass credentials over UNIX domain
+sockets. However, the nul byte MUST be sent even on other kinds of
+socket, and even on operating systems that do not require a byte to be
+sent in order to transmit credentials. The text protocol described in
+this document begins after the single nul byte. If the first byte
+received from the client is not a nul byte, the server may disconnect
+that client.
+
+A nul byte in any context other than the initial byte is an error;
+the protocol is ASCII-only.
+
+The credentials sent along with the nul byte may be used with the
+SASL mechanism EXTERNAL.
+
AUTH Command
===
diff --git a/test/watch.c b/test/watch.c
index 3b525d37..4387a9d6 100644
--- a/test/watch.c
+++ b/test/watch.c
@@ -305,6 +305,9 @@ error_handler (DBusConnection *connection,
"Error on connection: %s\n",
dbus_result_to_string (error_code));
+ /* we don't want to be called again since we're dropping the connection */
+ dbus_connection_set_error_function (connection, NULL, NULL, NULL);
+
_dbus_list_remove (&connections, connection);
dbus_connection_unref (connection);
quit_mainloop ();