From 8aa50589cd1a57c4e792c3ac840505bbdf2e2f4c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 11 Mar 2011 22:35:16 +0100 Subject: [PATCH 4/4] transport: add new exec transport on Unix The "exec:" 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: exec:path=ssh,argv1=foobar,argv2=system-bus-bridge or: exec:path=pkexec,argv1=system-bus-bridge or even: exec: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. --- dbus/dbus-sysdeps-unix.c | 118 +++++++++++++++++++++++++++---- dbus/dbus-sysdeps-unix.h | 4 + dbus/dbus-transport-unix.c | 173 ++++++++++++++++++++++++++++++++++++++++++++ dbus/dbus-transport-unix.h | 3 + 4 files changed, 285 insertions(+), 13 deletions(-) diff --git a/dbus/dbus-sysdeps-unix.c b/dbus/dbus-sysdeps-unix.c index 3290081..f609014 100644 --- a/dbus/dbus-sysdeps-unix.c +++ b/dbus/dbus-sysdeps-unix.c @@ -826,8 +826,6 @@ _dbus_connect_unix_socket (const char *path, path, _dbus_strerror (errno)); _dbus_close (fd, NULL); - fd = -1; - return -1; } @@ -836,8 +834,6 @@ _dbus_connect_unix_socket (const char *path, _DBUS_ASSERT_ERROR_IS_SET (error); _dbus_close (fd, NULL); - fd = -1; - return -1; } @@ -845,6 +841,110 @@ _dbus_connect_unix_socket (const char *path, } /** + * Closes all file descriptors except the first three. + * + * @returns location of temp directory + */ +static void _close_all (void) +{ + int maxfds, i; + + maxfds = sysconf (_SC_OPEN_MAX); + /* Pick something reasonable if for some reason sysconf + * says unlimited. + */ + if (maxfds < 0) + maxfds = 1024; + /* close all inherited fds */ + for (i = 3; i < maxfds; i++) + close (i); +} + +/** + * 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 UNIX domain socket + * @param abstract #TRUE to use abstract namespace + * @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; + } + + 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) + { + 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 */ + + _close_all (); + + if (path[0] == '/') + execv (path, argv); + execvp (path, argv); + + _exit(1); + } + + 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 * system feature exists, in which case the other side of the connection does @@ -3135,15 +3235,7 @@ _read_subprocess_line_argv (const char *progpath, if (dup2 (errors_pipe[WRITE_END], 2) == -1) _exit (1); - maxfds = sysconf (_SC_OPEN_MAX); - /* Pick something reasonable if for some reason sysconf - * says unlimited. - */ - if (maxfds < 0) - maxfds = 1024; - /* close all inherited fds */ - for (i = 3; i < maxfds; i++) - close (i); + _close_all(); sigprocmask (SIG_SETMASK, &old_set, NULL); diff --git a/dbus/dbus-sysdeps-unix.h b/dbus/dbus-sysdeps-unix.h index d7022b0..c409f4a 100644 --- a/dbus/dbus-sysdeps-unix.h +++ b/dbus/dbus-sysdeps-unix.h @@ -72,6 +72,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); diff --git a/dbus/dbus-transport-unix.c b/dbus/dbus-transport-unix.c index a47756f..c09b13c 100644 --- a/dbus/dbus-transport-unix.c +++ b/dbus/dbus-transport-unix.c @@ -22,6 +22,9 @@ */ #include + +#include + #include "dbus-internals.h" #include "dbus-connection-internal.h" #include "dbus-transport-unix.h" @@ -108,6 +111,97 @@ _dbus_transport_new_for_domain_socket (const char *path, } /** + * 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. + */ +DBusTransport* +_dbus_transport_new_for_exec (const char *path, + char *const argv[], + DBusError *error) +{ + int fd; + DBusTransport *transport; + DBusString address; + unsigned i; + + _DBUS_ASSERT_ERROR_IS_CLEAR (error); + + if (!_dbus_string_init (&address)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return NULL; + } + + fd = -1; + + if (!_dbus_string_append (&address, "exec:path=") || + !_dbus_string_append (&address, path)) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_0; + } + + if (argv) + { + for (i = 0; argv[i]; i++) + { + char *escaped; + dbus_bool_t success; + + escaped = dbus_address_escape_value (argv[i]); + if (!escaped) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_0; + } + + success = _dbus_string_append_printf (&address, "exec:argv%i=%s", i, escaped); + dbus_free(escaped); + + if (!success) + { + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + goto failed_0; + } + } + } + + fd = _dbus_connect_exec (path, argv, error); + if (fd < 0) + { + _DBUS_ASSERT_ERROR_IS_SET (error); + goto failed_0; + } + + _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_1; + } + + _dbus_string_free (&address); + + return transport; + + failed_1: + _dbus_close_socket (fd, NULL); + failed_0: + _dbus_string_free (&address); + return NULL; +} + +/** * Opens platform specific transport types. * * @param entry the address entry to try opening @@ -170,6 +264,85 @@ _dbus_transport_open_platform_specific (DBusAddressEntry *entry, return DBUS_TRANSPORT_OPEN_OK; } } + else if (strcmp (method, "exec") == 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 pass 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; + size_t l; + + 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; + } + + l = strlen (p)+1; + argv[i] = dbus_new (char, l); + + if (!argv[i]) + { + dbus_free_string_array (argv); + dbus_set_error (error, DBUS_ERROR_NO_MEMORY, NULL); + return DBUS_TRANSPORT_OPEN_DID_NOT_CONNECT; + } + + memcpy (argv[i], p, l); + } + + *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) { diff --git a/dbus/dbus-transport-unix.h b/dbus/dbus-transport-unix.h index 783a831..c7d27ef 100644 --- a/dbus/dbus-transport-unix.h +++ b/dbus/dbus-transport-unix.h @@ -31,6 +31,9 @@ DBusTransport* _dbus_transport_new_for_domain_socket (const char *path, dbus_bool_t abstract, DBusError *error); +DBusTransport* _dbus_transport_new_for_exec (const char *path, + char *const argv[], + DBusError *error); DBUS_END_DECLS -- 1.7.4.1