#include "util.h"
#include "macro.h"
+#include "missing.h"
#include "sd-bus.h"
#include "bus-internal.h"
#include "bus-message.h"
#include "bus-type.h"
-#define WQUEUE_MAX 128
-
static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec);
static void bus_free(sd_bus *b) {
struct filter_callback *f;
+ struct object_callback *c;
unsigned i;
assert(b);
free(f);
}
+ while ((c = hashmap_steal_first(b->object_callbacks))) {
+ free(c->path);
+ free(c);
+ }
+
+ hashmap_free(b->object_callbacks);
+
free(b);
}
assert(reply);
- bus->state = BUS_RUNNING;
-
r = sd_bus_message_read(reply, "s", &s);
if (r < 0)
return r;
+ if (!service_name_is_valid(s) || s[0] != ':')
+ return -EBADMSG;
+
bus->unique_name = strdup(s);
if (!bus->unique_name)
return -ENOMEM;
+ bus->state = BUS_RUNNING;
+
return 1;
}
return r;
n = MAX(3 + 32 + 2 + sizeof("AGREE_UNIX_FD") - 1 + 2, b->rbuffer_size * 2);
+
+ if (n > BUS_AUTH_SIZE_MAX)
+ n = BUS_AUTH_SIZE_MAX;
+
+ if (b->rbuffer_size >= n)
+ return -ENOBUFS;
+
p = realloc(b->rbuffer, n);
if (!p)
return -ENOMEM;
return 1;
}
+static int bus_setup_fd(sd_bus *b) {
+ int one;
+
+ assert(b);
+
+ /* Enable SO_PASSCRED + SO_PASSEC. We try this on any socket,
+ * just in case. This is actually irrelavant for */
+ one = 1;
+ setsockopt(b->fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one));
+ setsockopt(b->fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one));
+
+ /* Increase the buffers to a MB */
+ fd_inc_rcvbuf(b->fd, 1024*1024);
+ fd_inc_sndbuf(b->fd, 1024*1024);
+
+ return 0;
+}
+
static int bus_start_auth(sd_bus *b) {
static const char auth_prefix[] = "\0AUTH EXTERNAL ";
static const char auth_suffix[] = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n";
b->fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (b->fd < 0) {
b->last_connect_error = errno;
- zero(b->sockaddr);
- continue;
+ 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);
return 1;
b->last_connect_error = errno;
- close_nointr_nofail(b->fd);
- b->fd = -1;
- zero(b->sockaddr);
- continue;
+ 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;
+ }
}
}
return -ENOMEM;
b->fd = fd;
- fd_nonblock(b->fd, true);
+
+ r = fd_nonblock(b->fd, true);
+ if (r < 0)
+ goto fail;
+
fd_cloexec(b->fd, true);
+ if (r < 0)
+ goto fail;
+
+ r = bus_setup_fd(b);
+ if (r < 0)
+ goto fail;
r = bus_start_auth(b);
- if (r < 0) {
- bus_free(b);
- return r;
- }
+ if (r < 0)
+ goto fail;
*ret = b;
return 0;
+
+fail:
+ bus_free(b);
+ return r;
}
void sd_bus_close(sd_bus *bus) {
return bus->fd >= 0;
}
-int sd_bus_is_running(sd_bus *bus) {
+int sd_bus_can_send(sd_bus *bus, char type) {
+ int r;
+
if (!bus)
return -EINVAL;
- if (bus->fd < 0)
- return -ENOTCONN;
+ if (type == SD_BUS_TYPE_UNIX_FD) {
+ r = bus_ensure_running(bus);
+ if (r < 0)
+ return r;
+
+ return bus->can_fds;
+ }
- return bus->state == BUS_RUNNING;
+ return bus_type_is_valid(type);
}
-int sd_bus_can_send(sd_bus *bus, char type) {
+int sd_bus_get_peer(sd_bus *bus, sd_id128_t *peer) {
+ int r;
if (!bus)
return -EINVAL;
- if (bus->state != BUS_RUNNING && bus->state != BUS_HELLO)
- return -EAGAIN;
+ if (!peer)
+ return -EINVAL;
- if (type == SD_BUS_TYPE_UNIX_FD)
- return bus->can_fds;
+ r = bus_ensure_running(bus);
+ if (r < 0)
+ return r;
- return bus_type_is_valid(type);
+ *peer = bus->peer;
+ return 0;
}
static int bus_seal_message(sd_bus *b, sd_bus_message *m) {
static int message_read_need(sd_bus *bus, size_t *need) {
uint32_t a, b;
uint8_t e;
+ uint64_t sum;
assert(bus);
assert(need);
} else
return -EBADMSG;
- *need = sizeof(struct bus_header) + ALIGN_TO(b, 8) + a;
+ sum = (uint64_t) sizeof(struct bus_header) + (uint64_t) ALIGN_TO(b, 8) + (uint64_t) a;
+ if (sum >= BUS_MESSAGE_SIZE_MAX)
+ return -ENOBUFS;
+
+ *need = (size_t) sum;
return 0;
}
}
}
- r = bus_message_from_malloc(bus->rbuffer, size, &t);
+ r = bus_message_from_malloc(bus->rbuffer, size,
+ bus->ucred_valid ? &bus->ucred : NULL,
+ bus->label[0] ? bus->label : NULL, &t);
if (r < 0) {
free(b);
return r;
size_t need;
int r;
void *b;
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(struct ucred)) +
+ CMSG_SPACE(NAME_MAX)]; /*selinux label */
+ } control;
+ struct cmsghdr *cmsg;
assert(bus);
assert(m);
zero(mh);
mh.msg_iov = &iov;
mh.msg_iovlen = 1;
+ mh.msg_control = &control;
+ mh.msg_controllen = sizeof(control);
- k = recvmsg(bus->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL);
+ k = recvmsg(bus->fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC);
if (k < 0)
return errno == EAGAIN ? 0 : -errno;
bus->rbuffer_size += k;
+ bus->ucred_valid = false;
+ bus->label[0] = 0;
+
+ for (cmsg = CMSG_FIRSTHDR(&mh); cmsg; cmsg = CMSG_NXTHDR(&mh, cmsg)) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_CREDENTIALS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
+
+ memcpy(&bus->ucred, CMSG_DATA(cmsg), sizeof(struct ucred));
+ bus->ucred_valid = true;
+
+ } else if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_SECURITY) {
+
+ size_t l;
+ l = cmsg->cmsg_len - CMSG_LEN(0);
+ memcpy(&bus->label, CMSG_DATA(cmsg), l);
+ bus->label[l] = 0;
+ }
+ }
r = message_read_need(bus, &need);
if (r < 0)
}
static int dispatch_rqueue(sd_bus *bus, sd_bus_message **m) {
- sd_bus_message *z;
+ sd_bus_message *z = NULL;
int r, ret = 0;
assert(bus);
/* Just append it to the queue. */
- if (bus->wqueue_size >= WQUEUE_MAX)
+ if (bus->wqueue_size >= BUS_WQUEUE_MAX)
return -ENOBUFS;
q = realloc(bus->wqueue, sizeof(sd_bus_message*) * (bus->wqueue_size + 1));
return 1;
}
-static int ensure_running(sd_bus *bus) {
+int bus_ensure_running(sd_bus *bus) {
int r;
assert(bus);
- r = sd_bus_is_running(bus);
- if (r != 0)
- return r;
+ if (bus->state == BUS_RUNNING)
+ return 1;
for (;;) {
- int k;
-
r = sd_bus_process(bus, NULL);
-
if (r < 0)
return r;
-
- k = sd_bus_is_running(bus);
- if (k != 0)
- return k;
-
+ if (bus->state == BUS_RUNNING)
+ return 1;
if (r > 0)
continue;
if (bus_error_is_dirty(error))
return -EINVAL;
- r = ensure_running(bus);
+ r = bus_ensure_running(bus);
if (r < 0)
return r;
if (!room) {
sd_bus_message **q;
+ if (bus->rqueue_size >= BUS_RQUEUE_MAX)
+ return -ENOBUFS;
+
/* Make sure there's room for queuing this
* locally, before we read the message */
return r < 0 ? r : 1;
}
-static int process_message(sd_bus *bus, sd_bus_message *m) {
- struct filter_callback *l;
+static int process_reply(sd_bus *bus, sd_bus_message *m) {
+ struct reply_callback *c;
int r;
assert(bus);
assert(m);
- if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_CALL || m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_RETURN) {
- struct reply_callback *c;
+ if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_RETURN &&
+ m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_ERROR)
+ return 0;
- c = hashmap_remove(bus->reply_callbacks, &m->reply_serial);
- if (c) {
- if (c->timeout != 0)
- prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx);
+ c = hashmap_remove(bus->reply_callbacks, &m->reply_serial);
+ if (!c)
+ return 0;
- r = c->callback(bus, 0, m, c->userdata);
- free(c);
+ if (c->timeout != 0)
+ prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx);
- if (r != 0)
- return r;
- }
- }
+ r = c->callback(bus, 0, m, c->userdata);
+ free(c);
+
+ return r;
+}
+
+static int process_filter(sd_bus *bus, sd_bus_message *m) {
+ struct filter_callback *l;
+ int r;
LIST_FOREACH(callbacks, l, bus->filter_callbacks) {
r = l->callback(bus, 0, m, l->userdata);
return 0;
}
+static int process_builtin(sd_bus *bus, sd_bus_message *m) {
+ _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL)
+ return 0;
+
+ if (!streq_ptr(m->interface, "org.freedesktop.DBus.Peer"))
+ return 0;
+
+ if (m->header->flags & SD_BUS_MESSAGE_NO_REPLY_EXPECTED)
+ return 1;
+
+ if (streq_ptr(m->member, "Ping"))
+ r = sd_bus_message_new_method_return(bus, m, &reply);
+ else if (streq_ptr(m->member, "GetMachineId")) {
+ sd_id128_t id;
+ char sid[33];
+
+ r = sd_id128_get_machine(&id);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_new_method_return(bus, m, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(reply, "s", sd_id128_to_string(id, sid));
+ } else {
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_INIT;
+
+ sd_bus_error_set(&error,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ "Unknown method '%s' on interface '%s'.", m->member, m->interface);
+
+ r = sd_bus_message_new_method_error(bus, m, &error, &reply);
+ }
+
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int process_object(sd_bus *bus, sd_bus_message *m) {
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_INIT;
+ _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+ struct object_callback *c;
+ char *p;
+ int r;
+ bool found = false;
+
+ assert(bus);
+ assert(m);
+
+ if (m->header->type != SD_BUS_MESSAGE_TYPE_METHOD_CALL)
+ return 0;
+
+ if (hashmap_isempty(bus->object_callbacks))
+ return 0;
+
+ c = hashmap_get(bus->object_callbacks, m->path);
+ if (c) {
+ r = c->callback(bus, 0, m, c->userdata);
+ if (r != 0)
+ return r;
+
+ found = true;
+ }
+
+ /* Look for fallback prefixes */
+ p = strdupa(m->path);
+ for (;;) {
+ char *e;
+
+ e = strrchr(p, '/');
+ if (e == p || !e)
+ break;
+
+ *e = 0;
+
+ c = hashmap_get(bus->object_callbacks, p);
+ if (c && c->is_fallback) {
+ r = c->callback(bus, 0, m, c->userdata);
+ if (r != 0)
+ return r;
+
+ found = true;
+ }
+ }
+
+ if (!found)
+ return 0;
+
+ sd_bus_error_set(&error,
+ "org.freedesktop.DBus.Error.UnknownMethod",
+ "Unknown method '%s' or interface '%s'.", m->member, m->interface);
+
+ r = sd_bus_message_new_method_error(bus, m, &error, &reply);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_send(bus, reply, NULL);
+ if (r < 0)
+ return r;
+
+ return 1;
+}
+
+static int process_message(sd_bus *bus, sd_bus_message *m) {
+ int r;
+
+ assert(bus);
+ assert(m);
+
+ r = process_reply(bus, m);
+ if (r != 0)
+ return r;
+
+ r = process_filter(bus, m);
+ if (r != 0)
+ return r;
+
+ r = process_builtin(bus, m);
+ if (r != 0)
+ return r;
+
+ return process_object(bus, m);
+}
+
int sd_bus_process(sd_bus *bus, sd_bus_message **ret) {
int r;
return 1;
}
- if (sd_bus_message_is_method_call(m, NULL, NULL)) {
- const sd_bus_error e = SD_BUS_ERROR_INIT_CONST("org.freedesktop.DBus.Error.UnknownObject", "Unknown object.");
+ if (m->header->type == SD_BUS_MESSAGE_TYPE_METHOD_CALL) {
_cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+ _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_INIT;
- r = sd_bus_message_new_method_error(bus, m, &e, &reply);
+ sd_bus_error_set(&error, "org.freedesktop.DBus.Error.UnknownObject", "Unknown object '%s'.", m->path);
+
+ r = sd_bus_message_new_method_error(bus, m, &error, &reply);
if (r < 0)
return r;
if (bus->fd < 0)
return -ENOTCONN;
- r = ensure_running(bus);
+ r = bus_ensure_running(bus);
if (r < 0)
return r;
return 0;
}
+
+static int bus_add_object(
+ sd_bus *bus,
+ bool fallback,
+ const char *path,
+ sd_message_handler_t callback,
+ void *userdata) {
+
+ struct object_callback *c;
+ int r;
+
+ if (!bus)
+ return -EINVAL;
+ if (!path)
+ return -EINVAL;
+ if (!callback)
+ return -EINVAL;
+
+ r = hashmap_ensure_allocated(&bus->object_callbacks, string_hash_func, string_compare_func);
+ if (r < 0)
+ return r;
+
+ c = new(struct object_callback, 1);
+ if (!c)
+ return -ENOMEM;
+
+ c->path = strdup(path);
+ if (!path) {
+ free(c);
+ return -ENOMEM;
+ }
+
+ c->callback = callback;
+ c->userdata = userdata;
+ c->is_fallback = fallback;
+
+ r = hashmap_put(bus->object_callbacks, c->path, c);
+ if (r < 0) {
+ free(c->path);
+ free(c);
+ return r;
+ }
+
+ return 0;
+}
+
+static int bus_remove_object(
+ sd_bus *bus,
+ bool fallback,
+ const char *path,
+ sd_message_handler_t callback,
+ void *userdata) {
+
+ struct object_callback *c;
+
+ if (!bus)
+ return -EINVAL;
+ if (!path)
+ return -EINVAL;
+ if (!callback)
+ return -EINVAL;
+
+ c = hashmap_get(bus->object_callbacks, path);
+ if (!c)
+ return 0;
+
+ if (c->callback != callback || c->userdata != userdata || c->is_fallback != fallback)
+ return 0;
+
+ assert_se(c == hashmap_remove(bus->object_callbacks, c->path));
+
+ free(c->path);
+ free(c);
+
+ return 1;
+}
+
+int sd_bus_add_object(sd_bus *bus, const char *path, sd_message_handler_t callback, void *userdata) {
+ return bus_add_object(bus, false, path, callback, userdata);
+}
+
+int sd_bus_remove_object(sd_bus *bus, const char *path, sd_message_handler_t callback, void *userdata) {
+ return bus_remove_object(bus, false, path, callback, userdata);
+}
+
+int sd_bus_add_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t callback, void *userdata) {
+ return bus_add_object(bus, true, prefix, callback, userdata);
+}
+
+int sd_bus_remove_fallback(sd_bus *bus, const char *prefix, sd_message_handler_t callback, void *userdata) {
+ return bus_remove_object(bus, true, prefix, callback, userdata);
+}