2003-01-04 Havoc Pennington <hp@pobox.com>
authorHavoc Pennington <hp@redhat.com>
Sat, 4 Jan 2003 07:28:54 +0000 (07:28 +0000)
committerHavoc Pennington <hp@redhat.com>
Sat, 4 Jan 2003 07:28:54 +0000 (07:28 +0000)
* 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

12 files changed:
ChangeLog
configure.in
dbus/dbus-auth.c
dbus/dbus-auth.h
dbus/dbus-server-unix.c
dbus/dbus-sysdeps.c
dbus/dbus-sysdeps.h
dbus/dbus-transport-protected.h
dbus/dbus-transport-unix.c
dbus/dbus-transport.c
doc/dbus-sasl-profile.txt
test/watch.c

index 94f8beb..3008d92 100644 (file)
--- 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),
index a2b8169..9435c9d 100644 (file)
@@ -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.
index d39a777..032e49a 100644 (file)
@@ -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;
+    }
+}
+
+
 /** @} */
index 824426a..7346f99 100644 (file)
@@ -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;
 
index 66494bd..c9a494e 100644 (file)
@@ -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)
         {
index fed8814..f85a4a9 100644 (file)
@@ -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 */
index 17d96b3..73482f0 100644 (file)
@@ -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;
 
index 5105165..b9bbb40 100644 (file)
@@ -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,
index dd0c683..ba1528c 100644 (file)
@@ -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;
 }
 
index 085b022..110153d 100644 (file)
@@ -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;
     }
 }
index 44c756a..c2e8cd7 100644 (file)
@@ -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
 ===
   
index 3b525d3..4387a9d 100644 (file)
@@ -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 ();