]> git.ipfire.org Git - thirdparty/dbus.git/commitdiff
transport: add new unixexec transport on Unix
authorLennart Poettering <lennart@poettering.net>
Fri, 11 Mar 2011 21:35:16 +0000 (22:35 +0100)
committerSimon McVittie <simon.mcvittie@collabora.co.uk>
Mon, 12 Mar 2012 16:38:56 +0000 (16:38 +0000)
The "unixexec:" transport will create a local AF_UNIX socket with
socketpair(), then fork and execute a binary on one side with STDIN and
STDOUT connected to it and then use the other side.

This is useful to implement D-Bus tunneling schemes, for example to get
a D-Bus connection to the system bus on a different host, similar how
udisks is already doing it. (udisks uses SSH TCP tunneling for this,
which is a bit ugly and less secure than this solution).

Suggested use is with connection strings like the following:

  unixexec:path=ssh,argv1=foobar,argv2=system-bus-bridge

or:

  unixexec:path=pkexec,argv1=system-bus-bridge

or even:

  unixexec:path=sudo,argv1=system-bus-bridge

The first line would execute the binary 'system-bus-bridge' on host
'foobar' and then pass D-Bus traffic to it. This (hypothetical) bridge
binary would then forward the information to the local system bus.

The second and third line use this scheme locally to acquire a
privileged connection through pkexec resp. sudo: instead of connecting
directly to the bus, they use the same bridge binary which will forward
all information to the system bus.

The arguments of the protocol are 'path' for the first execlp()
argument, and argv0, argv1, and so on for the following arguments. argv0
can be left out in which case path will be used.

Bug: https://bugs.freedesktop.org/show_bug.cgi?id=35230
Reviewed-by: Simon McVittie <simon.mcvittie@collabora.co.uk>
dbus/dbus-connection-internal.h
dbus/dbus-connection.c
dbus/dbus-sysdeps-unix.c
dbus/dbus-sysdeps-unix.h
dbus/dbus-test.c
dbus/dbus-test.h
dbus/dbus-transport-unix.c

index ac4158e9ef50487c0e9a8aeac7204ea887a82238..3d37f188d0aec607808276ecd3445f53ce07ea1e 100644 (file)
@@ -114,6 +114,10 @@ void _dbus_connection_get_stats (DBusConnection *connection,
                                  dbus_uint32_t  *out_peak_bytes,
                                  dbus_uint32_t  *out_peak_fds);
 
+
+/* if DBUS_BUILD_TESTS */
+const char* _dbus_connection_get_address (DBusConnection *connection);
+
 /* This _dbus_bus_* stuff doesn't really belong here, but dbus-bus-internal.h seems
  * silly for one function
  */
index 74a9007c15b2439df83313aab987be9aa17b7d5e..ac0b2c0e7313ab67bbab305c2e77417236673229 100644 (file)
@@ -6258,4 +6258,18 @@ dbus_connection_get_outgoing_unix_fds (DBusConnection *connection)
   return res;
 }
 
+#ifdef DBUS_BUILD_TESTS
+/**
+ * Returns the address of the transport object of this connection
+ *
+ * @param connection the connection
+ * @returns the address string
+ */
+const char*
+_dbus_connection_get_address (DBusConnection *connection)
+{
+  return _dbus_transport_get_address (connection->transport);
+}
+#endif
+
 /** @} */
index 5770812398a98e3b84c60cdbd561786fb756f1b0..9fddca73da333c3d0c927b3fcd8daceece7402a1 100644 (file)
@@ -865,6 +865,96 @@ _dbus_connect_unix_socket (const char     *path,
   return fd;
 }
 
+/**
+ * Creates a UNIX domain socket and connects it to the specified
+ * process to execute.
+ *
+ * This will set FD_CLOEXEC for the socket returned.
+ *
+ * @param path the path to the executable
+ * @param argv the argument list for the process to execute.
+ * argv[0] typically is identical to the path of the executable
+ * @param error return location for error code
+ * @returns connection file descriptor or -1 on error
+ */
+int
+_dbus_connect_exec (const char     *path,
+                    char *const    argv[],
+                    DBusError      *error)
+{
+  int fds[2];
+  pid_t pid;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+  _dbus_verbose ("connecting to process %s\n", path);
+
+  if (socketpair (AF_UNIX, SOCK_STREAM
+#ifdef SOCK_CLOEXEC
+                  |SOCK_CLOEXEC
+#endif
+                  , 0, fds) < 0)
+    {
+      dbus_set_error (error,
+                      _dbus_error_from_errno (errno),
+                      "Failed to create socket pair: %s",
+                      _dbus_strerror (errno));
+      return -1;
+    }
+
+  _dbus_fd_set_close_on_exec (fds[0]);
+  _dbus_fd_set_close_on_exec (fds[1]);
+
+  pid = fork ();
+  if (pid < 0)
+    {
+      dbus_set_error (error,
+                      _dbus_error_from_errno (errno),
+                      "Failed to fork() to call %s: %s",
+                      path, _dbus_strerror (errno));
+      close (fds[0]);
+      close (fds[1]);
+      return -1;
+    }
+
+  if (pid == 0)
+    {
+      /* child */
+      close (fds[0]);
+
+      dup2 (fds[1], STDIN_FILENO);
+      dup2 (fds[1], STDOUT_FILENO);
+
+      if (fds[1] != STDIN_FILENO &&
+          fds[1] != STDOUT_FILENO)
+        close (fds[1]);
+
+      /* Inherit STDERR and the controlling terminal from the
+         parent */
+
+      _dbus_close_all ();
+
+      execvp (path, argv);
+
+      fprintf (stderr, "Failed to execute process %s: %s\n", path, _dbus_strerror (errno));
+
+      _exit(1);
+    }
+
+  /* parent */
+  close (fds[1]);
+
+  if (!_dbus_set_fd_nonblocking (fds[0], error))
+    {
+      _DBUS_ASSERT_ERROR_IS_SET (error);
+
+      close (fds[0]);
+      return -1;
+    }
+
+  return fds[0];
+}
+
 /**
  * Enables or disables the reception of credentials on the given socket during
  * the next message transmission.  This is only effective if the #LOCAL_CREDS
index fbe52590e8eafc703190ba44d788c87a975798f5..9b7089672c4bd04be7a378d07565abbc9240f3ff 100644 (file)
@@ -70,6 +70,10 @@ int _dbus_listen_unix_socket  (const char     *path,
                                dbus_bool_t     abstract,
                                DBusError      *error);
 
+int _dbus_connect_exec (const char     *path,
+                        char *const    argv[],
+                        DBusError      *error);
+
 int _dbus_listen_systemd_sockets (int       **fd,
                                  DBusError *error);
 
index 02eaf89bcf451eed5bbf5e63c4bb9d4dd0e44334..224a6c8cee6f594b622a4375e81cd5b2b7abb510 100644 (file)
@@ -154,6 +154,8 @@ dbus_internal_do_not_use_run_tests (const char *test_data_dir, const char *speci
 
 #ifdef DBUS_UNIX
   run_data_test ("userdb", specific_test, _dbus_userdb_test, test_data_dir);
+
+  run_test ("transport-unix", specific_test, _dbus_transport_unix_test);
 #endif
   
   run_test ("keyring", specific_test, _dbus_keyring_test);
index d97d142aa92bb2ea124d433604d53ba3a136075e..f254388b21bc2d7ba408dfb1f57f0763ab6bccc6 100644 (file)
@@ -48,6 +48,7 @@ dbus_bool_t _dbus_data_slot_test         (void);
 dbus_bool_t _dbus_sysdeps_test           (void);
 dbus_bool_t _dbus_spawn_test             (const char *test_data_dir);
 dbus_bool_t _dbus_userdb_test            (const char *test_data_dir);
+dbus_bool_t _dbus_transport_unix_test    (void);
 dbus_bool_t _dbus_memory_test            (void);
 dbus_bool_t _dbus_object_tree_test       (void);
 dbus_bool_t _dbus_credentials_test       (const char *test_data_dir);
index a47756f2e56bf9e643c5e87a885c88f0a216c52b..6ba5c0b5384a7210c689b78141cd0ad4f69c1a3e 100644 (file)
@@ -22,6 +22,9 @@
  */
 
 #include <config.h>
+
+#include <stdio.h>
+
 #include "dbus-internals.h"
 #include "dbus-connection-internal.h"
 #include "dbus-transport-unix.h"
@@ -29,6 +32,7 @@
 #include "dbus-transport-protected.h"
 #include "dbus-watch.h"
 #include "dbus-sysdeps-unix.h"
+#include "dbus-test.h"
 
 /**
  * @defgroup DBusTransportUnix DBusTransport implementations for UNIX
@@ -107,6 +111,108 @@ _dbus_transport_new_for_domain_socket (const char     *path,
   return NULL;
 }
 
+/**
+ * Creates a new transport for the given binary and arguments. This
+ * creates a client-side of a transport. The process will be forked
+ * off and executed with stdin/stdout connected to a local AF_UNIX
+ * socket.
+ *
+ * @param path the path to the domain socket.
+ * @param argv Parameters list
+ * @param error address where an error can be returned.
+ * @returns a new transport, or #NULL on failure.
+ */
+static DBusTransport*
+_dbus_transport_new_for_exec (const char     *path,
+                              char *const     argv[],
+                              DBusError      *error)
+{
+  int fd;
+  DBusTransport *transport;
+  DBusString address;
+  unsigned i;
+  char *escaped;
+
+  _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+  if (!_dbus_string_init (&address))
+    {
+      dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+      return NULL;
+    }
+
+  fd = -1;
+
+  escaped = dbus_address_escape_value (path);
+  if (!escaped)
+    {
+      dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+      goto failed;
+    }
+
+  if (!_dbus_string_append (&address, "unixexec:path=") ||
+      !_dbus_string_append (&address, escaped))
+    {
+      dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+      dbus_free (escaped);
+      goto failed;
+    }
+
+  dbus_free (escaped);
+
+  if (argv)
+    {
+      for (i = 0; argv[i]; i++)
+        {
+          dbus_bool_t success;
+
+          escaped = dbus_address_escape_value (argv[i]);
+          if (!escaped)
+            {
+              dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+              goto failed;
+            }
+
+          success = _dbus_string_append_printf (&address, ",argv%u=%s", i, escaped);
+          dbus_free (escaped);
+
+          if (!success)
+            {
+              dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+              goto failed;
+            }
+        }
+    }
+
+  fd = _dbus_connect_exec (path, argv, error);
+  if (fd < 0)
+    {
+      _DBUS_ASSERT_ERROR_IS_SET (error);
+      goto failed;
+    }
+
+  _dbus_verbose ("Successfully connected to process %s\n",
+                 path);
+
+  transport = _dbus_transport_new_for_socket (fd, NULL, &address);
+  if (transport == NULL)
+    {
+      dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+      goto failed;
+    }
+
+  _dbus_string_free (&address);
+
+  return transport;
+
+ failed:
+  if (fd >= 0)
+    _dbus_close_socket (fd, NULL);
+
+  _dbus_string_free (&address);
+  return NULL;
+}
+
 /**
  * Opens platform specific transport types.
  * 
@@ -168,7 +274,81 @@ _dbus_transport_open_platform_specific (DBusAddressEntry  *entry,
         {
           _DBUS_ASSERT_ERROR_IS_CLEAR (error);
           return DBUS_TRANSPORT_OPEN_OK;
-        }      
+        }
+    }
+  else if (strcmp (method, "unixexec") == 0)
+    {
+      const char *path;
+      unsigned i;
+      char **argv;
+
+      path = dbus_address_entry_get_value (entry, "path");
+      if (path == NULL)
+        {
+          _dbus_set_bad_address (error, NULL, NULL,
+                                 "No process path specified");
+          return DBUS_TRANSPORT_OPEN_BAD_ADDRESS;
+        }
+
+      /* First count argv arguments */
+      for (i = 1; ; i++)
+        {
+          char t[4+20+1]; /* "argv" plus space for a formatted base 10 64bit integer, plus NUL */
+
+          snprintf (t, sizeof(t), "argv%u", i);
+
+          if (!dbus_address_entry_get_value (entry, t))
+            break;
+        }
+
+      /* Allocate string array */
+      argv = dbus_new0 (char*, i+1);
+      if (!argv)
+        {
+          dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+          return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT;
+        }
+
+      /* Fill in string array */
+      for (i = 0; ; i++)
+        {
+          char t[4+20+1];
+          const char *p;
+
+          snprintf (t, sizeof(t), "argv%u", i);
+
+          p = dbus_address_entry_get_value (entry, t);
+          if (!p)
+            {
+              if (i == 0)
+                /* If argv0 isn't specified, fill in the path instead */
+                p = path;
+              else
+                break;
+            }
+
+          argv[i] = _dbus_strdup (p);
+          if (!argv[i])
+            {
+              dbus_free_string_array (argv);
+              dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL);
+              return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT;
+            }
+        }
+
+      *transport_p = _dbus_transport_new_for_exec (path, argv, error);
+      dbus_free_string_array (argv);
+
+      if (*transport_p == NULL)
+        {
+          _DBUS_ASSERT_ERROR_IS_SET (error);
+          return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT;
+        }
+      else
+        {
+          _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+          return DBUS_TRANSPORT_OPEN_OK;
+        }
     }
 #ifdef DBUS_ENABLE_LAUNCHD
   else if (strcmp (method, "launchd") == 0)
@@ -231,3 +411,32 @@ _dbus_transport_open_platform_specific (DBusAddressEntry  *entry,
 }
 
 /** @} */
+
+#ifdef DBUS_BUILD_TESTS
+
+dbus_bool_t
+_dbus_transport_unix_test (void)
+{
+  DBusConnection *c;
+  DBusError error;
+  dbus_bool_t ret;
+  const char *address;
+
+  dbus_error_init (&error);
+
+  c = dbus_connection_open ("unixexec:argv0=false,argv1=foobar,path=/bin/false", &error);
+  _dbus_assert (c != NULL);
+  _dbus_assert (!dbus_error_is_set (&error));
+
+  address = _dbus_connection_get_address (c);
+  _dbus_assert (address != NULL);
+
+  /* Let's see if the address got parsed, reordered and formatted correctly */
+  ret = strcmp (address, "unixexec:path=/bin/false,argv0=false,argv1=foobar") == 0;
+
+  dbus_connection_unref (c);
+
+  return ret;
+}
+
+#endif