bus: implement 'unixexec:' protocol

This commit is contained in:
Lennart Poettering 2013-03-25 02:30:32 +01:00
parent 021a1e78d7
commit 2fd9ae2e9b
5 changed files with 436 additions and 160 deletions

View File

@ -120,6 +120,9 @@ struct sd_bus {
int *fds;
unsigned n_fds;
char *exec_path;
char **exec_argv;
};
static inline void bus_unrefp(sd_bus **b) {
@ -145,6 +148,8 @@ static inline void bus_unrefp(sd_bus **b) {
#define BUS_FDS_MAX 1024
#define BUS_EXEC_ARGV_MAX 256
bool object_path_is_valid(const char *p);
bool interface_name_is_valid(const char *p);
bool service_name_is_valid(const char *p);

View File

@ -30,6 +30,7 @@
#include "util.h"
#include "macro.h"
#include "missing.h"
#include "strv.h"
#include "sd-bus.h"
#include "bus-internal.h"
@ -53,6 +54,9 @@ static void bus_free(sd_bus *b) {
free(b->auth_uid);
free(b->address);
free(b->exec_path);
strv_free(b->exec_argv);
close_many(b->fds, b->n_fds);
free(b->fds);
@ -141,6 +145,37 @@ int sd_bus_set_fd(sd_bus *bus, int fd) {
return 0;
}
int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]) {
char *p, **a;
if (!bus)
return -EINVAL;
if (bus->state != BUS_UNSET)
return -EPERM;
if (!path)
return -EINVAL;
if (strv_isempty(argv))
return -EINVAL;
p = strdup(path);
if (!p)
return -ENOMEM;
a = strv_copy(argv);
if (!a) {
free(p);
return -ENOMEM;
}
free(bus->exec_path);
strv_free(bus->exec_argv);
bus->exec_path = p;
bus->exec_argv = a;
return 0;
}
int sd_bus_set_hello(sd_bus *bus, int b) {
if (!bus)
return -EINVAL;
@ -234,21 +269,24 @@ static int parse_address_key(const char **p, const char *key, char **value) {
assert(p);
assert(*p);
assert(key);
assert(value);
l = strlen(key);
if (strncmp(*p, key, l) != 0)
return 0;
if (key) {
l = strlen(key);
if (strncmp(*p, key, l) != 0)
return 0;
if ((*p)[l] != '=')
return 0;
if ((*p)[l] != '=')
return 0;
if (*value)
return -EINVAL;
if (*value)
return -EINVAL;
a = *p + l + 1;
while (*a != ',' && *a != 0) {
a = *p + l + 1;
} else
a = *p;
while (*a != ';' && *a != ',' && *a != 0) {
char c, *t;
if (*a == '%') {
@ -294,7 +332,10 @@ static int parse_address_key(const char **p, const char *key, char **value) {
a++;
*p = a;
free(*value);
*value = r;
return 1;
}
@ -308,9 +349,242 @@ static void skip_address_key(const char **p) {
(*p) ++;
}
static int parse_unix_address(sd_bus *b, const char **p, char **guid) {
_cleanup_free_ char *path = NULL, *abstract = NULL;
size_t l;
int r;
assert(b);
assert(p);
assert(*p);
assert(guid);
while (**p != 0 && **p != ';') {
r = parse_address_key(p, "guid", guid);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(p, "path", &path);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(p, "abstract", &abstract);
if (r < 0)
return r;
else if (r > 0)
continue;
skip_address_key(p);
}
if (!path && !abstract)
return -EINVAL;
if (path && abstract)
return -EINVAL;
if (path) {
l = strlen(path);
if (l > sizeof(b->sockaddr.un.sun_path))
return -E2BIG;
b->sockaddr.un.sun_family = AF_UNIX;
strncpy(b->sockaddr.un.sun_path, path, sizeof(b->sockaddr.un.sun_path));
b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l;
} else if (abstract) {
l = strlen(abstract);
if (l > sizeof(b->sockaddr.un.sun_path) - 1)
return -E2BIG;
b->sockaddr.un.sun_family = AF_UNIX;
b->sockaddr.un.sun_path[0] = 0;
strncpy(b->sockaddr.un.sun_path+1, abstract, sizeof(b->sockaddr.un.sun_path)-1);
b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
}
return 0;
}
static int parse_tcp_address(sd_bus *b, const char **p, char **guid) {
_cleanup_free_ char *host = NULL, *port = NULL, *family = NULL;
struct addrinfo hints, *result;
int r;
assert(b);
assert(p);
assert(*p);
assert(guid);
while (**p != 0 && **p != ';') {
r = parse_address_key(p, "guid", guid);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(p, "host", &host);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(p, "port", &port);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(p, "family", &family);
if (r < 0)
return r;
else if (r > 0)
continue;
skip_address_key(p);
}
if (!host || !port)
return -EINVAL;
zero(hints);
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
if (family) {
if (streq(family, "ipv4"))
hints.ai_family = AF_INET;
else if (streq(family, "ipv6"))
hints.ai_family = AF_INET6;
else
return -EINVAL;
}
r = getaddrinfo(host, port, &hints, &result);
if (r == EAI_SYSTEM)
return -errno;
else if (r != 0)
return -EADDRNOTAVAIL;
memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen);
b->sockaddr_size = result->ai_addrlen;
freeaddrinfo(result);
return 0;
}
static int parse_exec_address(sd_bus *b, const char **p, char **guid) {
char *path = NULL;
unsigned n_argv = 0, j;
char **argv = NULL;
int r;
assert(b);
assert(p);
assert(*p);
assert(guid);
while (**p != 0 && **p != ';') {
r = parse_address_key(p, "guid", guid);
if (r < 0)
goto fail;
else if (r > 0)
continue;
r = parse_address_key(p, "path", &path);
if (r < 0)
goto fail;
else if (r > 0)
continue;
if (startswith(*p, "argv")) {
unsigned ul;
errno = 0;
ul = strtoul(*p + 4, (char**) p, 10);
if (errno != 0 || **p != '=' || ul > 256) {
r = -EINVAL;
goto fail;
}
(*p) ++;
if (ul >= n_argv) {
char **x;
x = realloc(argv, sizeof(char*) * (ul + 2));
if (!x) {
r = -ENOMEM;
goto fail;
}
memset(x + n_argv, 0, sizeof(char*) * (ul - n_argv + 2));
argv = x;
n_argv = ul + 1;
}
r = parse_address_key(p, NULL, argv + ul);
if (r < 0)
goto fail;
continue;
}
skip_address_key(p);
}
if (!path)
goto fail;
/* Make sure there are no holes in the array, with the
* exception of argv[0] */
for (j = 1; j < n_argv; j++)
if (!argv[j]) {
r = -EINVAL;
goto fail;
}
if (argv && argv[0] == NULL) {
argv[0] = strdup(path);
if (!argv[0]) {
r = -ENOMEM;
goto fail;
}
}
b->exec_path = path;
b->exec_argv = argv;
return 0;
fail:
for (j = 0; j < n_argv; j++)
free(argv[j]);
free(argv);
free(path);
return r;
}
static void bus_reset_parsed_address(sd_bus *b) {
assert(b);
zero(b->sockaddr);
b->sockaddr_size = 0;
strv_free(b->exec_argv);
free(b->exec_path);
b->exec_path = NULL;
b->exec_argv = NULL;
b->peer = SD_ID128_NULL;
}
static int bus_parse_next_address(sd_bus *b) {
const char *a, *p;
_cleanup_free_ char *guid = NULL;
const char *a;
int r;
assert(b);
@ -320,126 +594,48 @@ static int bus_parse_next_address(sd_bus *b) {
if (b->address[b->address_index] == 0)
return 0;
bus_reset_parsed_address(b);
a = b->address + b->address_index;
zero(b->sockaddr);
b->sockaddr_size = 0;
b->peer = SD_ID128_NULL;
while (*a != 0) {
if (startswith(a, "unix:")) {
_cleanup_free_ char *path = NULL, *abstract = NULL;
p = a + 5;
while (*p != 0) {
r = parse_address_key(&p, "guid", &guid);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(&p, "path", &path);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(&p, "abstract", &abstract);
if (r < 0)
return r;
else if (r > 0)
continue;
skip_address_key(&p);
if (*a == ';') {
a++;
continue;
}
if (!path && !abstract)
return -EINVAL;
if (startswith(a, "unix:")) {
a += 5;
if (path && abstract)
return -EINVAL;
r = parse_unix_address(b, &a, &guid);
if (r < 0)
return r;
break;
if (path) {
size_t l;
} else if (startswith(a, "tcp:")) {
l = strlen(path);
if (l > sizeof(b->sockaddr.un.sun_path))
return -E2BIG;
a += 4;
r = parse_tcp_address(b, &a, &guid);
if (r < 0)
return r;
b->sockaddr.un.sun_family = AF_UNIX;
strncpy(b->sockaddr.un.sun_path, path, sizeof(b->sockaddr.un.sun_path));
b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l;
} else if (abstract) {
size_t l;
break;
l = strlen(abstract);
if (l > sizeof(b->sockaddr.un.sun_path) - 1)
return -E2BIG;
} else if (startswith(a, "unixexec:")) {
a += 9;
r = parse_exec_address(b, &a, &guid);
if (r < 0)
return r;
break;
b->sockaddr.un.sun_family = AF_UNIX;
b->sockaddr.un.sun_path[0] = 0;
strncpy(b->sockaddr.un.sun_path+1, abstract, sizeof(b->sockaddr.un.sun_path)-1);
b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l;
}
} else if (startswith(a, "tcp:")) {
_cleanup_free_ char *host = NULL, *port = NULL, *family = NULL;
struct addrinfo hints, *result;
p = a + 4;
while (*p != 0) {
r = parse_address_key(&p, "guid", &guid);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(&p, "host", &host);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(&p, "port", &port);
if (r < 0)
return r;
else if (r > 0)
continue;
r = parse_address_key(&p, "family", &family);
if (r < 0)
return r;
else if (r > 0)
continue;
skip_address_key(&p);
}
if (!host || !port)
return -EINVAL;
zero(hints);
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_ADDRCONFIG;
if (family) {
if (streq(family, "ipv4"))
hints.ai_family = AF_INET;
else if (streq(family, "ipv6"))
hints.ai_family = AF_INET6;
else
return -EINVAL;
}
r = getaddrinfo(host, port, &hints, &result);
if (r == EAI_SYSTEM)
return -errno;
else if (r != 0)
return -EADDRNOTAVAIL;
memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen);
b->sockaddr_size = result->ai_addrlen;
freeaddrinfo(result);
a = strchr(a, ';');
if (!a)
return 0;
}
if (guid) {
@ -448,7 +644,7 @@ static int bus_parse_next_address(sd_bus *b) {
return r;
}
b->address_index = p - b->address;
b->address_index = a - b->address;
return 1;
}
@ -643,11 +839,21 @@ static int bus_start_auth(sd_bus *b) {
char text[20 + 1]; /* enough space for a 64bit integer plus NUL */
size_t l;
const char *auth_suffix;
int domain = 0, r;
socklen_t sl;
assert(b);
b->state = BUS_AUTHENTICATING;
sl = sizeof(domain);
r = getsockopt(b->fd, SOL_SOCKET, SO_DOMAIN, &domain, &sl);
if (r < 0)
return -errno;
if (domain != AF_UNIX)
b->negotiate_fds = false;
snprintf(text, sizeof(text), "%llu", (unsigned long long) geteuid());
char_array_0(text);
@ -669,51 +875,114 @@ static int bus_start_auth(sd_bus *b) {
return bus_write_auth(b);
}
static int bus_start_connect(sd_bus *b) {
static int bus_connect(sd_bus *b) {
int r;
assert(b);
assert(b->fd < 0);
assert(b->sockaddr.sa.sa_family != AF_UNSPEC);
b->fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (b->fd < 0)
return -errno;
r = bus_setup_fd(b);
if (r < 0)
return r;
r = connect(b->fd, &b->sockaddr.sa, b->sockaddr_size);
if (r < 0) {
if (errno == EINPROGRESS)
return 1;
return -errno;
}
return bus_start_auth(b);
}
static int bus_exec(sd_bus *b) {
int s[2];
pid_t pid;
assert(b);
assert(b->fd < 0);
assert(b->exec_path);
b->fd = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, s);
if (b->fd < 0)
return -errno;
pid = fork();
if (pid < 0) {
close_pipe(s);
return -errno;
}
if (pid == 0) {
/* Child */
close_all_fds(s, 2);
close_nointr_nofail(s[0]);
assert_se(dup3(s[1], STDIN_FILENO, 0) == STDIN_FILENO);
assert_se(dup3(s[1], STDOUT_FILENO, 0) == STDOUT_FILENO);
if (s[1] != STDIN_FILENO && s[1] != STDOUT_FILENO)
close_nointr_nofail(s[1]);
fd_cloexec(STDIN_FILENO, false);
fd_cloexec(STDOUT_FILENO, false);
fd_nonblock(STDIN_FILENO, false);
fd_nonblock(STDOUT_FILENO, false);
if (b->exec_argv)
execvp(b->exec_path, b->exec_argv);
else {
const char *argv[] = { b->exec_path, NULL };
execvp(b->exec_path, (char**) argv);
}
_exit(EXIT_FAILURE);
}
close_nointr_nofail(s[1]);
b->fd = s[0];
return bus_start_auth(b);
}
static int bus_start_connect(sd_bus *b) {
int r;
assert(b);
for (;;) {
if (b->sockaddr.sa.sa_family == AF_UNSPEC) {
r = bus_parse_next_address(b);
if (r < 0)
return r;
if (r == 0)
return b->last_connect_error ? -b->last_connect_error : -ECONNREFUSED;
}
b->fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (b->fd < 0) {
b->last_connect_error = errno;
goto try_again;
}
r = bus_setup_fd(b);
if (r < 0) {
b->last_connect_error = errno;
goto try_again;
}
r = connect(b->fd, &b->sockaddr.sa, b->sockaddr_size);
if (r < 0) {
if (errno == EINPROGRESS)
return 1;
b->last_connect_error = errno;
goto try_again;
}
return bus_start_auth(b);
try_again:
zero(b->sockaddr);
if (b->fd >= 0) {
close_nointr_nofail(b->fd);
b->fd = -1;
}
if (b->sockaddr.sa.sa_family != AF_UNSPEC) {
r = bus_connect(b);
if (r >= 0)
return r;
b->last_connect_error = -r;
} else if (b->exec_path) {
r = bus_exec(b);
if (r >= 0)
return r;
b->last_connect_error = -r;
}
r = bus_parse_next_address(b);
if (r < 0)
return r;
if (r == 0)
return b->last_connect_error ? -b->last_connect_error : -ECONNREFUSED;
}
}
@ -1893,6 +2162,7 @@ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) {
}
/* Try next address */
bus_reset_parsed_address(bus);
r = bus_start_connect(bus);
goto null_message;
}

View File

@ -29,8 +29,8 @@
/* TODO:
* - implicitly add stub introspection calls
* - implement unix exec protocol
* - server side
* - split out actual sending logic into backend-socket.c
*
* Later:
* - add page donation logic
@ -57,6 +57,7 @@ int sd_bus_open_user(sd_bus **ret);
int sd_bus_new(sd_bus **ret);
int sd_bus_set_address(sd_bus *bus, const char *address);
int sd_bus_set_fd(sd_bus *bus, int fd);
int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]);
int sd_bus_set_hello(sd_bus *bus, int b);
int sd_bus_set_negotiate_fds(sd_bus *bus, int b);
int sd_bus_start(sd_bus *ret);

View File

@ -64,7 +64,7 @@ void strv_free(char **l) {
free(l);
}
char **strv_copy(char **l) {
char **strv_copy(char * const *l) {
char **r, **k;
k = r = new(char*, strv_length(l) + 1);
@ -84,7 +84,7 @@ char **strv_copy(char **l) {
return r;
}
unsigned strv_length(char **l) {
unsigned strv_length(char * const *l) {
unsigned n = 0;
if (!l)

View File

@ -34,8 +34,8 @@ static inline void strv_freep(char ***l) {
strv_free(*l);
}
char **strv_copy(char **l) _malloc_;
unsigned strv_length(char **l);
char **strv_copy(char * const *l) _malloc_;
unsigned strv_length(char * const *l);
char **strv_merge(char **a, char **b);
char **strv_merge_concat(char **a, char **b, const char *suffix);
@ -56,7 +56,7 @@ static inline const char* STRV_IFNOTNULL(const char *x) {
return x ? x : (const char *) -1;
}
static inline bool strv_isempty(char **l) {
static inline bool strv_isempty(char * const *l) {
return !l || !*l;
}