diff options
Diffstat (limited to 'src/bus-proxyd')
-rw-r--r-- | src/bus-proxyd/Makefile | 75 | ||||
-rw-r--r-- | src/bus-proxyd/bus-proxyd.c | 328 | ||||
-rw-r--r-- | src/bus-proxyd/bus-xml-policy.c | 1327 | ||||
-rw-r--r-- | src/bus-proxyd/bus-xml-policy.h | 147 | ||||
-rw-r--r-- | src/bus-proxyd/driver.c | 745 | ||||
-rw-r--r-- | src/bus-proxyd/driver.h | 27 | ||||
-rw-r--r-- | src/bus-proxyd/proxy.c | 953 | ||||
-rw-r--r-- | src/bus-proxyd/proxy.h | 66 | ||||
-rw-r--r-- | src/bus-proxyd/stdio-bridge.c | 244 | ||||
-rw-r--r-- | src/bus-proxyd/synthesize.c | 225 | ||||
-rw-r--r-- | src/bus-proxyd/synthesize.h | 36 | ||||
-rw-r--r-- | src/bus-proxyd/test-bus-xml-policy.c | 170 |
12 files changed, 4343 insertions, 0 deletions
diff --git a/src/bus-proxyd/Makefile b/src/bus-proxyd/Makefile new file mode 100644 index 0000000000..4a267340b9 --- /dev/null +++ b/src/bus-proxyd/Makefile @@ -0,0 +1,75 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. +noinst_LTLIBRARIES += \ + libbus-proxy-core.la + +libbus_proxy_core_la_SOURCES = \ + src/bus-proxyd/bus-xml-policy.c \ + src/bus-proxyd/bus-xml-policy.h \ + src/bus-proxyd/driver.c \ + src/bus-proxyd/driver.h \ + src/bus-proxyd/proxy.c \ + src/bus-proxyd/proxy.h \ + src/bus-proxyd/synthesize.c \ + src/bus-proxyd/synthesize.h + +libbus_proxy_core_la_LIBADD = \ + libshared.la + +systemd_bus_proxyd_SOURCES = \ + src/bus-proxyd/bus-proxyd.c + +systemd_bus_proxyd_LDADD = \ + libbus-proxy-core.la \ + libshared.la + +systemd_stdio_bridge_SOURCES = \ + src/bus-proxyd/stdio-bridge.c + +systemd_stdio_bridge_LDADD = \ + libbus-proxy-core.la \ + libshared.la + +nodist_systemunit_DATA += \ + units/systemd-bus-proxyd.service + +dist_systemunit_DATA += \ + units/systemd-bus-proxyd.socket + +nodist_userunit_DATA += \ + units/user/systemd-bus-proxyd.service + +dist_userunit_DATA += \ + units/user/systemd-bus-proxyd.socket + +EXTRA_DIST += \ + units/systemd-bus-proxyd.service.m4.in \ + units/user/systemd-bus-proxyd.service.in + +if HAVE_SMACK +bus-proxyd-set-cap-hook: + -$(SETCAP) cap_mac_admin+ei $(DESTDIR)$(rootlibexecdir)/systemd-bus-proxyd + +INSTALL_EXEC_HOOKS += bus-proxyd-set-cap-hook +endif + diff --git a/src/bus-proxyd/bus-proxyd.c b/src/bus-proxyd/bus-proxyd.c new file mode 100644 index 0000000000..b6bd6eb390 --- /dev/null +++ b/src/bus-proxyd/bus-proxyd.c @@ -0,0 +1,328 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Daniel Mack + Copyright 2014 Kay Sievers + Copyright 2015 David Herrmann + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <getopt.h> +#include <pthread.h> +#include <stddef.h> +#include <string.h> +#include <sys/prctl.h> +#include <sys/socket.h> +#include <unistd.h> + +#include "sd-daemon.h" + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-xml-policy.h" +#include "capability-util.h" +#include "def.h" +#include "fd-util.h" +#include "formats-util.h" +#include "log.h" +#include "proxy.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +static char *arg_address = NULL; +static char **arg_configuration = NULL; + +typedef struct { + int fd; + SharedPolicy *policy; + uid_t bus_uid; +} ClientContext; + +static ClientContext *client_context_free(ClientContext *c) { + if (!c) + return NULL; + + safe_close(c->fd); + free(c); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(ClientContext*, client_context_free); + +static int client_context_new(ClientContext **out) { + _cleanup_(client_context_freep) ClientContext *c = NULL; + + c = new0(ClientContext, 1); + if (!c) + return -ENOMEM; + + c->fd = -1; + + *out = c; + c = NULL; + return 0; +} + +static void *run_client(void *userdata) { + _cleanup_(client_context_freep) ClientContext *c = userdata; + _cleanup_(proxy_freep) Proxy *p = NULL; + char comm[16]; + int r; + + r = proxy_new(&p, c->fd, c->fd, arg_address); + c->fd = -1; + + if (r < 0) + goto exit; + + /* set comm to "p$PIDu$UID" and suffix with '*' if truncated */ + r = snprintf(comm, sizeof(comm), "p" PID_FMT "u" UID_FMT, p->local_creds.pid, p->local_creds.uid); + if (r >= (ssize_t)sizeof(comm)) + comm[sizeof(comm) - 2] = '*'; + (void) prctl(PR_SET_NAME, comm); + + r = proxy_set_policy(p, c->policy, arg_configuration); + if (r < 0) + goto exit; + + r = proxy_hello_policy(p, c->bus_uid); + if (r < 0) + goto exit; + + r = proxy_run(p); + +exit: + return NULL; +} + +static int loop_clients(int accept_fd, uid_t bus_uid) { + _cleanup_(shared_policy_freep) SharedPolicy *sp = NULL; + pthread_attr_t attr; + int r; + + r = pthread_attr_init(&attr); + if (r != 0) + return log_error_errno(r, "Cannot initialize pthread attributes: %m"); + + r = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + if (r != 0) { + r = log_error_errno(r, "Cannot mark pthread attributes as detached: %m"); + goto finish; + } + + r = shared_policy_new(&sp); + if (r < 0) + goto finish; + + for (;;) { + ClientContext *c; + pthread_t tid; + int fd; + + fd = accept4(accept_fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC); + if (fd < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + + r = log_error_errno(errno, "accept4() failed: %m"); + goto finish; + } + + r = client_context_new(&c); + if (r < 0) { + log_oom(); + close(fd); + continue; + } + + c->fd = fd; + c->policy = sp; + c->bus_uid = bus_uid; + + r = pthread_create(&tid, &attr, run_client, c); + if (r != 0) { + log_warning_errno(r, "Cannot spawn thread, ignoring: %m"); + client_context_free(c); + continue; + } + } + +finish: + pthread_attr_destroy(&attr); + return r; +} + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "DBus proxy server.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --configuration=PATH Configuration file or directory\n" + " --machine=MACHINE Connect to specified machine\n" + " --address=ADDRESS Connect to the bus specified by ADDRESS\n" + " (default: " DEFAULT_SYSTEM_BUS_ADDRESS ")\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_ADDRESS, + ARG_CONFIGURATION, + ARG_MACHINE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "address", required_argument, NULL, ARG_ADDRESS }, + { "configuration", required_argument, NULL, ARG_CONFIGURATION }, + { "machine", required_argument, NULL, ARG_MACHINE }, + {}, + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_ADDRESS: + r = free_and_strdup(&arg_address, optarg); + if (r < 0) + return log_oom(); + break; + + case ARG_CONFIGURATION: + r = strv_extend(&arg_configuration, optarg); + if (r < 0) + return log_oom(); + break; + + case ARG_MACHINE: { + _cleanup_free_ char *e = NULL; + char *a; + + e = bus_address_escape(optarg); + if (!e) + return log_oom(); + + a = strjoin("x-machine-kernel:machine=", e, ";x-machine-unix:machine=", e, NULL); + if (!a) + return log_oom(); + + free(arg_address); + arg_address = a; + + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc > optind) { + log_error("Too many arguments"); + return -EINVAL; + } + + if (!arg_address) { + arg_address = strdup(DEFAULT_SYSTEM_BUS_ADDRESS); + if (!arg_address) + return log_oom(); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, accept_fd; + uid_t uid, bus_uid; + gid_t gid; + + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_parse_environment(); + log_open(); + + bus_uid = getuid(); + + if (geteuid() == 0) { + const char *user = "systemd-bus-proxy"; + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Cannot resolve user name %s: %m", user); + goto finish; + } + + r = drop_privileges(uid, gid, 1ULL << CAP_IPC_OWNER); + if (r < 0) { + log_error_errno(r, "Cannot drop privileges: %m"); + goto finish; + } + } + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_listen_fds(0); + if (r != 1) { + log_error("Illegal number of file descriptors passed"); + goto finish; + } + + accept_fd = SD_LISTEN_FDS_START; + + r = fd_nonblock(accept_fd, false); + if (r < 0) { + log_error_errno(r, "Cannot mark accept-fd non-blocking: %m"); + goto finish; + } + + r = loop_clients(accept_fd, bus_uid); + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down."); + + strv_free(arg_configuration); + free(arg_address); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/bus-proxyd/bus-xml-policy.c b/src/bus-proxyd/bus-xml-policy.c new file mode 100644 index 0000000000..8943e0dc12 --- /dev/null +++ b/src/bus-proxyd/bus-xml-policy.c @@ -0,0 +1,1327 @@ +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-login.h" + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-xml-policy.h" +#include "conf-files.h" +#include "fileio.h" +#include "formats-util.h" +#include "locale-util.h" +#include "set.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "xml.h" + +static void policy_item_free(PolicyItem *i) { + assert(i); + + free(i->interface); + free(i->member); + free(i->error); + free(i->name); + free(i->path); + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(PolicyItem*, policy_item_free); + +static void item_append(PolicyItem *i, PolicyItem **list) { + + PolicyItem *tail; + + LIST_FIND_TAIL(items, *list, tail); + LIST_INSERT_AFTER(items, *list, tail, i); +} + +static int file_load(Policy *p, const char *path) { + + _cleanup_free_ char *c = NULL, *policy_user = NULL, *policy_group = NULL; + _cleanup_(policy_item_freep) PolicyItem *i = NULL; + void *xml_state = NULL; + unsigned n_other = 0; + const char *q; + int r; + + enum { + STATE_OUTSIDE, + STATE_BUSCONFIG, + STATE_POLICY, + STATE_POLICY_CONTEXT, + STATE_POLICY_CONSOLE, + STATE_POLICY_USER, + STATE_POLICY_GROUP, + STATE_POLICY_OTHER_ATTRIBUTE, + STATE_ALLOW_DENY, + STATE_ALLOW_DENY_INTERFACE, + STATE_ALLOW_DENY_MEMBER, + STATE_ALLOW_DENY_ERROR, + STATE_ALLOW_DENY_PATH, + STATE_ALLOW_DENY_MESSAGE_TYPE, + STATE_ALLOW_DENY_NAME, + STATE_ALLOW_DENY_OTHER_ATTRIBUTE, + STATE_OTHER, + } state = STATE_OUTSIDE; + + enum { + POLICY_CATEGORY_NONE, + POLICY_CATEGORY_DEFAULT, + POLICY_CATEGORY_MANDATORY, + POLICY_CATEGORY_ON_CONSOLE, + POLICY_CATEGORY_NO_CONSOLE, + POLICY_CATEGORY_USER, + POLICY_CATEGORY_GROUP + } policy_category = POLICY_CATEGORY_NONE; + + unsigned line = 0; + + assert(p); + + r = read_full_file(path, &c, NULL); + if (r < 0) { + if (r == -ENOENT) + return 0; + if (r == -EISDIR) + return r; + + return log_error_errno(r, "Failed to load %s: %m", path); + } + + q = c; + for (;;) { + _cleanup_free_ char *name = NULL; + int t; + + t = xml_tokenize(&q, &name, &xml_state, &line); + if (t < 0) + return log_error_errno(t, "XML parse failure in %s: %m", path); + + switch (state) { + + case STATE_OUTSIDE: + + if (t == XML_TAG_OPEN) { + if (streq(name, "busconfig")) + state = STATE_BUSCONFIG; + else { + log_error("Unexpected tag %s at %s:%u.", name, path, line); + return -EINVAL; + } + + } else if (t == XML_END) + return 0; + else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token (1) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_BUSCONFIG: + + if (t == XML_TAG_OPEN) { + if (streq(name, "policy")) { + state = STATE_POLICY; + policy_category = POLICY_CATEGORY_NONE; + free(policy_user); + free(policy_group); + policy_user = policy_group = NULL; + } else { + state = STATE_OTHER; + n_other = 0; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq(name, "busconfig"))) + state = STATE_OUTSIDE; + else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token (2) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_POLICY: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq(name, "context")) + state = STATE_POLICY_CONTEXT; + else if (streq(name, "at_console")) + state = STATE_POLICY_CONSOLE; + else if (streq(name, "user")) + state = STATE_POLICY_USER; + else if (streq(name, "group")) + state = STATE_POLICY_GROUP; + else { + log_warning("Attribute %s of <policy> tag unknown at %s:%u, ignoring.", name, path, line); + state = STATE_POLICY_OTHER_ATTRIBUTE; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq(name, "policy"))) + state = STATE_BUSCONFIG; + else if (t == XML_TAG_OPEN) { + PolicyItemType it; + + if (streq(name, "allow")) + it = POLICY_ITEM_ALLOW; + else if (streq(name, "deny")) + it = POLICY_ITEM_DENY; + else { + log_warning("Unknown tag %s in <policy> %s:%u.", name, path, line); + return -EINVAL; + } + + assert(!i); + i = new0(PolicyItem, 1); + if (!i) + return log_oom(); + + i->type = it; + state = STATE_ALLOW_DENY; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token (3) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_POLICY_CONTEXT: + + if (t == XML_ATTRIBUTE_VALUE) { + if (streq(name, "default")) { + policy_category = POLICY_CATEGORY_DEFAULT; + state = STATE_POLICY; + } else if (streq(name, "mandatory")) { + policy_category = POLICY_CATEGORY_MANDATORY; + state = STATE_POLICY; + } else { + log_error("context= parameter %s unknown for <policy> at %s:%u.", name, path, line); + return -EINVAL; + } + } else { + log_error("Unexpected token (4) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_POLICY_CONSOLE: + + if (t == XML_ATTRIBUTE_VALUE) { + if (streq(name, "true")) { + policy_category = POLICY_CATEGORY_ON_CONSOLE; + state = STATE_POLICY; + } else if (streq(name, "false")) { + policy_category = POLICY_CATEGORY_NO_CONSOLE; + state = STATE_POLICY; + } else { + log_error("at_console= parameter %s unknown for <policy> at %s:%u.", name, path, line); + return -EINVAL; + } + } else { + log_error("Unexpected token (4.1) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_POLICY_USER: + + if (t == XML_ATTRIBUTE_VALUE) { + free(policy_user); + policy_user = name; + name = NULL; + policy_category = POLICY_CATEGORY_USER; + state = STATE_POLICY; + } else { + log_error("Unexpected token (5) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_POLICY_GROUP: + + if (t == XML_ATTRIBUTE_VALUE) { + free(policy_group); + policy_group = name; + name = NULL; + policy_category = POLICY_CATEGORY_GROUP; + state = STATE_POLICY; + } else { + log_error("Unexpected token (6) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_POLICY_OTHER_ATTRIBUTE: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_POLICY; + else { + log_error("Unexpected token (7) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY: + + assert(i); + + if (t == XML_ATTRIBUTE_NAME) { + PolicyItemClass ic; + + if (startswith(name, "send_")) + ic = POLICY_ITEM_SEND; + else if (startswith(name, "receive_")) + ic = POLICY_ITEM_RECV; + else if (streq(name, "own")) + ic = POLICY_ITEM_OWN; + else if (streq(name, "own_prefix")) + ic = POLICY_ITEM_OWN_PREFIX; + else if (streq(name, "user")) + ic = POLICY_ITEM_USER; + else if (streq(name, "group")) + ic = POLICY_ITEM_GROUP; + else if (STR_IN_SET(name, "eavesdrop", "log")) { + log_debug("Unsupported attribute %s= at %s:%u, ignoring.", name, path, line); + state = STATE_ALLOW_DENY_OTHER_ATTRIBUTE; + break; + } else { + log_error("Unknown attribute %s= at %s:%u, ignoring.", name, path, line); + state = STATE_ALLOW_DENY_OTHER_ATTRIBUTE; + break; + } + + if (i->class != _POLICY_ITEM_CLASS_UNSET && ic != i->class) { + log_error("send_, receive_/eavesdrop fields mixed on same tag at %s:%u.", path, line); + return -EINVAL; + } + + i->class = ic; + + if (ic == POLICY_ITEM_SEND || ic == POLICY_ITEM_RECV) { + const char *u; + + u = strchr(name, '_'); + assert(u); + + u++; + + if (streq(u, "interface")) + state = STATE_ALLOW_DENY_INTERFACE; + else if (streq(u, "member")) + state = STATE_ALLOW_DENY_MEMBER; + else if (streq(u, "error")) + state = STATE_ALLOW_DENY_ERROR; + else if (streq(u, "path")) + state = STATE_ALLOW_DENY_PATH; + else if (streq(u, "type")) + state = STATE_ALLOW_DENY_MESSAGE_TYPE; + else if ((streq(u, "destination") && ic == POLICY_ITEM_SEND) || + (streq(u, "sender") && ic == POLICY_ITEM_RECV)) + state = STATE_ALLOW_DENY_NAME; + else { + if (streq(u, "requested_reply")) + log_debug("Unsupported attribute %s= at %s:%u, ignoring.", name, path, line); + else + log_error("Unknown attribute %s= at %s:%u, ignoring.", name, path, line); + state = STATE_ALLOW_DENY_OTHER_ATTRIBUTE; + break; + } + } else + state = STATE_ALLOW_DENY_NAME; + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq(name, i->type == POLICY_ITEM_ALLOW ? "allow" : "deny"))) { + + /* If the tag is fully empty so far, we consider it a recv */ + if (i->class == _POLICY_ITEM_CLASS_UNSET) + i->class = POLICY_ITEM_RECV; + + if (policy_category == POLICY_CATEGORY_DEFAULT) + item_append(i, &p->default_items); + else if (policy_category == POLICY_CATEGORY_MANDATORY) + item_append(i, &p->mandatory_items); + else if (policy_category == POLICY_CATEGORY_ON_CONSOLE) + item_append(i, &p->on_console_items); + else if (policy_category == POLICY_CATEGORY_NO_CONSOLE) + item_append(i, &p->no_console_items); + else if (policy_category == POLICY_CATEGORY_USER) { + const char *u = policy_user; + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + + r = hashmap_ensure_allocated(&p->user_items, NULL); + if (r < 0) + return log_oom(); + + if (!u) { + log_error("User policy without name"); + return -EINVAL; + } + + r = get_user_creds(&u, &i->uid, NULL, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Failed to resolve user %s, ignoring policy: %m", u); + free(i); + } else { + PolicyItem *first; + + first = hashmap_get(p->user_items, UID_TO_PTR(i->uid)); + item_append(i, &first); + i->uid_valid = true; + + r = hashmap_replace(p->user_items, UID_TO_PTR(i->uid), first); + if (r < 0) { + LIST_REMOVE(items, first, i); + return log_oom(); + } + } + + } else if (policy_category == POLICY_CATEGORY_GROUP) { + const char *g = policy_group; + + assert_cc(sizeof(gid_t) == sizeof(uint32_t)); + + r = hashmap_ensure_allocated(&p->group_items, NULL); + if (r < 0) + return log_oom(); + + if (!g) { + log_error("Group policy without name"); + return -EINVAL; + } + + r = get_group_creds(&g, &i->gid); + if (r < 0) { + log_error_errno(r, "Failed to resolve group %s, ignoring policy: %m", g); + free(i); + } else { + PolicyItem *first; + + first = hashmap_get(p->group_items, GID_TO_PTR(i->gid)); + item_append(i, &first); + i->gid_valid = true; + + r = hashmap_replace(p->group_items, GID_TO_PTR(i->gid), first); + if (r < 0) { + LIST_REMOVE(items, first, i); + return log_oom(); + } + } + } + + state = STATE_POLICY; + i = NULL; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token (8) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY_INTERFACE: + + if (t == XML_ATTRIBUTE_VALUE) { + assert(i); + if (i->interface) { + log_error("Duplicate interface at %s:%u.", path, line); + return -EINVAL; + } + + if (!streq(name, "*")) { + i->interface = name; + name = NULL; + } + state = STATE_ALLOW_DENY; + } else { + log_error("Unexpected token (9) at %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY_MEMBER: + + if (t == XML_ATTRIBUTE_VALUE) { + assert(i); + if (i->member) { + log_error("Duplicate member in %s:%u.", path, line); + return -EINVAL; + } + + if (!streq(name, "*")) { + i->member = name; + name = NULL; + } + state = STATE_ALLOW_DENY; + } else { + log_error("Unexpected token (10) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY_ERROR: + + if (t == XML_ATTRIBUTE_VALUE) { + assert(i); + if (i->error) { + log_error("Duplicate error in %s:%u.", path, line); + return -EINVAL; + } + + if (!streq(name, "*")) { + i->error = name; + name = NULL; + } + state = STATE_ALLOW_DENY; + } else { + log_error("Unexpected token (11) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY_PATH: + + if (t == XML_ATTRIBUTE_VALUE) { + assert(i); + if (i->path) { + log_error("Duplicate path in %s:%u.", path, line); + return -EINVAL; + } + + if (!streq(name, "*")) { + i->path = name; + name = NULL; + } + state = STATE_ALLOW_DENY; + } else { + log_error("Unexpected token (12) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY_MESSAGE_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + assert(i); + + if (i->message_type != 0) { + log_error("Duplicate message type in %s:%u.", path, line); + return -EINVAL; + } + + if (!streq(name, "*")) { + r = bus_message_type_from_string(name, &i->message_type); + if (r < 0) { + log_error("Invalid message type in %s:%u.", path, line); + return -EINVAL; + } + } + + state = STATE_ALLOW_DENY; + } else { + log_error("Unexpected token (13) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + assert(i); + if (i->name) { + log_error("Duplicate name in %s:%u.", path, line); + return -EINVAL; + } + + switch (i->class) { + case POLICY_ITEM_USER: + if (!streq(name, "*")) { + const char *u = name; + + r = get_user_creds(&u, &i->uid, NULL, NULL, NULL); + if (r < 0) + log_error_errno(r, "Failed to resolve user %s: %m", name); + else + i->uid_valid = true; + } + break; + case POLICY_ITEM_GROUP: + if (!streq(name, "*")) { + const char *g = name; + + r = get_group_creds(&g, &i->gid); + if (r < 0) + log_error_errno(r, "Failed to resolve group %s: %m", name); + else + i->gid_valid = true; + } + break; + + case POLICY_ITEM_SEND: + case POLICY_ITEM_RECV: + + if (streq(name, "*")) + name = mfree(name); + break; + + + default: + break; + } + + i->name = name; + name = NULL; + + state = STATE_ALLOW_DENY; + } else { + log_error("Unexpected token (14) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_ALLOW_DENY_OTHER_ATTRIBUTE: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_ALLOW_DENY; + else { + log_error("Unexpected token (15) in %s:%u.", path, line); + return -EINVAL; + } + + break; + + case STATE_OTHER: + + if (t == XML_TAG_OPEN) + n_other++; + else if (t == XML_TAG_CLOSE || t == XML_TAG_CLOSE_EMPTY) { + + if (n_other == 0) + state = STATE_BUSCONFIG; + else + n_other--; + } + + break; + } + } +} + +enum { + DENY, + ALLOW, + DUNNO, +}; + +static const char *verdict_to_string(int v) { + switch (v) { + + case DENY: + return "DENY"; + case ALLOW: + return "ALLOW"; + case DUNNO: + return "DUNNO"; + } + + return NULL; +} + +struct policy_check_filter { + PolicyItemClass class; + uid_t uid; + gid_t gid; + int message_type; + const char *name; + const char *interface; + const char *path; + const char *member; +}; + +static int is_permissive(PolicyItem *i) { + + assert(i); + + return (i->type == POLICY_ITEM_ALLOW) ? ALLOW : DENY; +} + +static int check_policy_item(PolicyItem *i, const struct policy_check_filter *filter) { + + assert(i); + assert(filter); + + switch (i->class) { + case POLICY_ITEM_SEND: + case POLICY_ITEM_RECV: + + if (i->name && !streq_ptr(i->name, filter->name)) + break; + + if ((i->message_type != 0) && (i->message_type != filter->message_type)) + break; + + if (i->path && !streq_ptr(i->path, filter->path)) + break; + + if (i->member && !streq_ptr(i->member, filter->member)) + break; + + if (i->interface && !streq_ptr(i->interface, filter->interface)) + break; + + return is_permissive(i); + + case POLICY_ITEM_OWN: + assert(filter->name); + + if (streq(i->name, "*") || streq(i->name, filter->name)) + return is_permissive(i); + break; + + case POLICY_ITEM_OWN_PREFIX: + assert(filter->name); + + if (streq(i->name, "*") || service_name_startswith(filter->name, i->name)) + return is_permissive(i); + break; + + case POLICY_ITEM_USER: + if (filter->uid != UID_INVALID) + if ((streq_ptr(i->name, "*") || (i->uid_valid && i->uid == filter->uid))) + return is_permissive(i); + break; + + case POLICY_ITEM_GROUP: + if (filter->gid != GID_INVALID) + if ((streq_ptr(i->name, "*") || (i->gid_valid && i->gid == filter->gid))) + return is_permissive(i); + break; + + case POLICY_ITEM_IGNORE: + default: + break; + } + + return DUNNO; +} + +static int check_policy_items(PolicyItem *items, const struct policy_check_filter *filter) { + + PolicyItem *i; + int verdict = DUNNO; + + assert(filter); + + /* Check all policies in a set - a broader one might be followed by a more specific one, + * and the order of rules in policy definitions matters */ + LIST_FOREACH(items, i, items) { + int v; + + if (i->class != filter->class && + !(i->class == POLICY_ITEM_OWN_PREFIX && filter->class == POLICY_ITEM_OWN)) + continue; + + v = check_policy_item(i, filter); + if (v != DUNNO) + verdict = v; + } + + return verdict; +} + +static int policy_check(Policy *p, const struct policy_check_filter *filter) { + + PolicyItem *items; + int verdict, v; + + assert(p); + assert(filter); + + assert(IN_SET(filter->class, POLICY_ITEM_SEND, POLICY_ITEM_RECV, POLICY_ITEM_OWN, POLICY_ITEM_USER, POLICY_ITEM_GROUP)); + + /* + * The policy check is implemented by the following logic: + * + * 1. Check default items + * 2. Check group items + * 3. Check user items + * 4. Check on/no_console items + * 5. Check mandatory items + * + * Later rules override earlier rules. + */ + + verdict = check_policy_items(p->default_items, filter); + + if (filter->gid != GID_INVALID) { + items = hashmap_get(p->group_items, GID_TO_PTR(filter->gid)); + if (items) { + v = check_policy_items(items, filter); + if (v != DUNNO) + verdict = v; + } + } + + if (filter->uid != UID_INVALID) { + items = hashmap_get(p->user_items, UID_TO_PTR(filter->uid)); + if (items) { + v = check_policy_items(items, filter); + if (v != DUNNO) + verdict = v; + } + } + + if (filter->uid != UID_INVALID && sd_uid_get_seats(filter->uid, -1, NULL) > 0) + v = check_policy_items(p->on_console_items, filter); + else + v = check_policy_items(p->no_console_items, filter); + if (v != DUNNO) + verdict = v; + + v = check_policy_items(p->mandatory_items, filter); + if (v != DUNNO) + verdict = v; + + return verdict; +} + +bool policy_check_own(Policy *p, uid_t uid, gid_t gid, const char *name) { + + struct policy_check_filter filter = { + .class = POLICY_ITEM_OWN, + .uid = uid, + .gid = gid, + .name = name, + }; + + int verdict; + + assert(p); + assert(name); + + verdict = policy_check(p, &filter); + + log_full(LOG_AUTH | (verdict != ALLOW ? LOG_WARNING : LOG_DEBUG), + "Ownership permission check for uid=" UID_FMT " gid=" GID_FMT" name=%s: %s", + uid, gid, strna(name), strna(verdict_to_string(verdict))); + + return verdict == ALLOW; +} + +bool policy_check_hello(Policy *p, uid_t uid, gid_t gid) { + + struct policy_check_filter filter = { + .uid = uid, + .gid = gid, + }; + int verdict; + + assert(p); + + filter.class = POLICY_ITEM_USER; + verdict = policy_check(p, &filter); + + if (verdict != DENY) { + int v; + + filter.class = POLICY_ITEM_GROUP; + v = policy_check(p, &filter); + if (v != DUNNO) + verdict = v; + } + + log_full(LOG_AUTH | (verdict != ALLOW ? LOG_WARNING : LOG_DEBUG), + "Hello permission check for uid=" UID_FMT " gid=" GID_FMT": %s", + uid, gid, strna(verdict_to_string(verdict))); + + return verdict == ALLOW; +} + +bool policy_check_one_recv(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + const char *name, + const char *path, + const char *interface, + const char *member) { + + struct policy_check_filter filter = { + .class = POLICY_ITEM_RECV, + .uid = uid, + .gid = gid, + .message_type = message_type, + .name = name, + .interface = interface, + .path = path, + .member = member, + }; + + assert(p); + + return policy_check(p, &filter) == ALLOW; +} + +bool policy_check_recv(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + Set *names, + char **namesv, + const char *path, + const char *interface, + const char *member, + bool dbus_to_kernel) { + + char *n, **nv, *last = NULL; + bool allow = false; + Iterator i; + + assert(p); + + if (set_isempty(names) && strv_isempty(namesv)) { + allow = policy_check_one_recv(p, uid, gid, message_type, NULL, path, interface, member); + } else { + SET_FOREACH(n, names, i) { + last = n; + allow = policy_check_one_recv(p, uid, gid, message_type, n, path, interface, member); + if (allow) + break; + } + if (!allow) { + STRV_FOREACH(nv, namesv) { + last = *nv; + allow = policy_check_one_recv(p, uid, gid, message_type, *nv, path, interface, member); + if (allow) + break; + } + } + } + + log_full(LOG_AUTH | (!allow ? LOG_WARNING : LOG_DEBUG), + "Receive permission check %s for uid=" UID_FMT " gid=" GID_FMT" message=%s name=%s path=%s interface=%s member=%s: %s", + dbus_to_kernel ? "dbus-1 to kernel" : "kernel to dbus-1", uid, gid, bus_message_type_to_string(message_type), strna(last), + strna(path), strna(interface), strna(member), allow ? "ALLOW" : "DENY"); + + return allow; +} + +bool policy_check_one_send(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + const char *name, + const char *path, + const char *interface, + const char *member) { + + struct policy_check_filter filter = { + .class = POLICY_ITEM_SEND, + .uid = uid, + .gid = gid, + .message_type = message_type, + .name = name, + .interface = interface, + .path = path, + .member = member, + }; + + assert(p); + + return policy_check(p, &filter) == ALLOW; +} + +bool policy_check_send(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + Set *names, + char **namesv, + const char *path, + const char *interface, + const char *member, + bool dbus_to_kernel, + char **out_used_name) { + + char *n, **nv, *last = NULL; + bool allow = false; + Iterator i; + + assert(p); + + if (set_isempty(names) && strv_isempty(namesv)) { + allow = policy_check_one_send(p, uid, gid, message_type, NULL, path, interface, member); + } else { + SET_FOREACH(n, names, i) { + last = n; + allow = policy_check_one_send(p, uid, gid, message_type, n, path, interface, member); + if (allow) + break; + } + if (!allow) { + STRV_FOREACH(nv, namesv) { + last = *nv; + allow = policy_check_one_send(p, uid, gid, message_type, *nv, path, interface, member); + if (allow) + break; + } + } + } + + if (out_used_name) + *out_used_name = last; + + log_full(LOG_AUTH | (!allow ? LOG_WARNING : LOG_DEBUG), + "Send permission check %s for uid=" UID_FMT " gid=" GID_FMT" message=%s name=%s path=%s interface=%s member=%s: %s", + dbus_to_kernel ? "dbus-1 to kernel" : "kernel to dbus-1", uid, gid, bus_message_type_to_string(message_type), strna(last), + strna(path), strna(interface), strna(member), allow ? "ALLOW" : "DENY"); + + return allow; +} + +int policy_load(Policy *p, char **files) { + char **i; + int r; + + assert(p); + + STRV_FOREACH(i, files) { + + r = file_load(p, *i); + if (r == -EISDIR) { + _cleanup_strv_free_ char **l = NULL; + char **j; + + r = conf_files_list(&l, ".conf", NULL, *i, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get configuration file list: %m"); + + STRV_FOREACH(j, l) + file_load(p, *j); + } + + /* We ignore all errors but EISDIR, and just proceed. */ + } + + return 0; +} + +void policy_free(Policy *p) { + PolicyItem *i, *first; + + if (!p) + return; + + while ((i = p->default_items)) { + LIST_REMOVE(items, p->default_items, i); + policy_item_free(i); + } + + while ((i = p->mandatory_items)) { + LIST_REMOVE(items, p->mandatory_items, i); + policy_item_free(i); + } + + while ((i = p->on_console_items)) { + LIST_REMOVE(items, p->on_console_items, i); + policy_item_free(i); + } + + while ((i = p->no_console_items)) { + LIST_REMOVE(items, p->no_console_items, i); + policy_item_free(i); + } + + while ((first = hashmap_steal_first(p->user_items))) { + + while ((i = first)) { + LIST_REMOVE(items, first, i); + policy_item_free(i); + } + } + + while ((first = hashmap_steal_first(p->group_items))) { + + while ((i = first)) { + LIST_REMOVE(items, first, i); + policy_item_free(i); + } + } + + hashmap_free(p->user_items); + hashmap_free(p->group_items); + + p->user_items = p->group_items = NULL; +} + +static void dump_items(PolicyItem *items, const char *prefix) { + + PolicyItem *i; + + if (!items) + return; + + if (!prefix) + prefix = ""; + + LIST_FOREACH(items, i, items) { + + printf("%sType: %s\n" + "%sClass: %s\n", + prefix, policy_item_type_to_string(i->type), + prefix, policy_item_class_to_string(i->class)); + + if (i->interface) + printf("%sInterface: %s\n", + prefix, i->interface); + + if (i->member) + printf("%sMember: %s\n", + prefix, i->member); + + if (i->error) + printf("%sError: %s\n", + prefix, i->error); + + if (i->path) + printf("%sPath: %s\n", + prefix, i->path); + + if (i->name) + printf("%sName: %s\n", + prefix, i->name); + + if (i->message_type != 0) + printf("%sMessage Type: %s\n", + prefix, bus_message_type_to_string(i->message_type)); + + if (i->uid_valid) { + _cleanup_free_ char *user; + + user = uid_to_name(i->uid); + + printf("%sUser: %s ("UID_FMT")\n", + prefix, strna(user), i->uid); + } + + if (i->gid_valid) { + _cleanup_free_ char *group; + + group = gid_to_name(i->gid); + + printf("%sGroup: %s ("GID_FMT")\n", + prefix, strna(group), i->gid); + } + printf("%s-\n", prefix); + } +} + +static void dump_hashmap_items(Hashmap *h) { + PolicyItem *i; + Iterator j; + void *k; + + HASHMAP_FOREACH_KEY(i, k, h, j) { + printf("\t%s Item for " UID_FMT ":\n", draw_special_char(DRAW_ARROW), PTR_TO_UID(k)); + dump_items(i, "\t\t"); + } +} + +void policy_dump(Policy *p) { + + printf("%s Default Items:\n", draw_special_char(DRAW_ARROW)); + dump_items(p->default_items, "\t"); + + printf("%s Group Items:\n", draw_special_char(DRAW_ARROW)); + dump_hashmap_items(p->group_items); + + printf("%s User Items:\n", draw_special_char(DRAW_ARROW)); + dump_hashmap_items(p->user_items); + + printf("%s On-Console Items:\n", draw_special_char(DRAW_ARROW)); + dump_items(p->on_console_items, "\t"); + + printf("%s No-Console Items:\n", draw_special_char(DRAW_ARROW)); + dump_items(p->no_console_items, "\t"); + + printf("%s Mandatory Items:\n", draw_special_char(DRAW_ARROW)); + dump_items(p->mandatory_items, "\t"); + + fflush(stdout); +} + +int shared_policy_new(SharedPolicy **out) { + SharedPolicy *sp; + int r; + + sp = new0(SharedPolicy, 1); + if (!sp) + return log_oom(); + + r = pthread_mutex_init(&sp->lock, NULL); + if (r != 0) { + r = log_error_errno(r, "Cannot initialize shared policy mutex: %m"); + goto exit_free; + } + + r = pthread_rwlock_init(&sp->rwlock, NULL); + if (r != 0) { + r = log_error_errno(r, "Cannot initialize shared policy rwlock: %m"); + goto exit_mutex; + } + + *out = sp; + sp = NULL; + return 0; + + /* pthread lock destruction is not fail-safe... meh! */ +exit_mutex: + pthread_mutex_destroy(&sp->lock); +exit_free: + free(sp); + return r; +} + +SharedPolicy *shared_policy_free(SharedPolicy *sp) { + if (!sp) + return NULL; + + policy_free(sp->policy); + pthread_rwlock_destroy(&sp->rwlock); + pthread_mutex_destroy(&sp->lock); + strv_free(sp->configuration); + free(sp); + + return NULL; +} + +static int shared_policy_reload_unlocked(SharedPolicy *sp, char **configuration) { + Policy old, buffer = {}; + bool free_old; + int r; + + assert(sp); + + r = policy_load(&buffer, configuration); + if (r < 0) + return log_error_errno(r, "Failed to load policy: %m"); + + log_debug("Reloading configuration"); + /* policy_dump(&buffer); */ + + pthread_rwlock_wrlock(&sp->rwlock); + memcpy(&old, &sp->buffer, sizeof(old)); + memcpy(&sp->buffer, &buffer, sizeof(buffer)); + free_old = !!sp->policy; + sp->policy = &sp->buffer; + pthread_rwlock_unlock(&sp->rwlock); + + if (free_old) + policy_free(&old); + + return 0; +} + +int shared_policy_reload(SharedPolicy *sp) { + int r; + + assert(sp); + + pthread_mutex_lock(&sp->lock); + r = shared_policy_reload_unlocked(sp, sp->configuration); + pthread_mutex_unlock(&sp->lock); + + return r; +} + +int shared_policy_preload(SharedPolicy *sp, char **configuration) { + _cleanup_strv_free_ char **conf = NULL; + int r = 0; + + assert(sp); + + conf = strv_copy(configuration); + if (!conf) + return log_oom(); + + pthread_mutex_lock(&sp->lock); + if (!sp->policy) { + r = shared_policy_reload_unlocked(sp, conf); + if (r >= 0) { + sp->configuration = conf; + conf = NULL; + } + } + pthread_mutex_unlock(&sp->lock); + + return r; +} + +Policy *shared_policy_acquire(SharedPolicy *sp) { + assert(sp); + + pthread_rwlock_rdlock(&sp->rwlock); + if (sp->policy) + return sp->policy; + pthread_rwlock_unlock(&sp->rwlock); + + return NULL; +} + +void shared_policy_release(SharedPolicy *sp, Policy *p) { + assert(sp); + assert(!p || sp->policy == p); + + if (p) + pthread_rwlock_unlock(&sp->rwlock); +} + +static const char* const policy_item_type_table[_POLICY_ITEM_TYPE_MAX] = { + [_POLICY_ITEM_TYPE_UNSET] = "unset", + [POLICY_ITEM_ALLOW] = "allow", + [POLICY_ITEM_DENY] = "deny", +}; +DEFINE_STRING_TABLE_LOOKUP(policy_item_type, PolicyItemType); + +static const char* const policy_item_class_table[_POLICY_ITEM_CLASS_MAX] = { + [_POLICY_ITEM_CLASS_UNSET] = "unset", + [POLICY_ITEM_SEND] = "send", + [POLICY_ITEM_RECV] = "recv", + [POLICY_ITEM_OWN] = "own", + [POLICY_ITEM_OWN_PREFIX] = "own-prefix", + [POLICY_ITEM_USER] = "user", + [POLICY_ITEM_GROUP] = "group", + [POLICY_ITEM_IGNORE] = "ignore", +}; +DEFINE_STRING_TABLE_LOOKUP(policy_item_class, PolicyItemClass); diff --git a/src/bus-proxyd/bus-xml-policy.h b/src/bus-proxyd/bus-xml-policy.h new file mode 100644 index 0000000000..3dcddaa048 --- /dev/null +++ b/src/bus-proxyd/bus-xml-policy.h @@ -0,0 +1,147 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <pthread.h> + +#include "hashmap.h" +#include "list.h" + +typedef enum PolicyItemType { + _POLICY_ITEM_TYPE_UNSET = 0, + POLICY_ITEM_ALLOW, + POLICY_ITEM_DENY, + _POLICY_ITEM_TYPE_MAX, + _POLICY_ITEM_TYPE_INVALID = -1, +} PolicyItemType; + +typedef enum PolicyItemClass { + _POLICY_ITEM_CLASS_UNSET = 0, + POLICY_ITEM_SEND, + POLICY_ITEM_RECV, + POLICY_ITEM_OWN, + POLICY_ITEM_OWN_PREFIX, + POLICY_ITEM_USER, + POLICY_ITEM_GROUP, + POLICY_ITEM_IGNORE, + _POLICY_ITEM_CLASS_MAX, + _POLICY_ITEM_CLASS_INVALID = -1, +} PolicyItemClass; + +typedef struct PolicyItem PolicyItem; + +struct PolicyItem { + PolicyItemType type; + PolicyItemClass class; + char *interface; + char *member; + char *error; + char *path; + char *name; + uint8_t message_type; + uid_t uid; + gid_t gid; + + bool uid_valid, gid_valid; + + LIST_FIELDS(PolicyItem, items); +}; + +typedef struct Policy { + LIST_HEAD(PolicyItem, default_items); + LIST_HEAD(PolicyItem, mandatory_items); + LIST_HEAD(PolicyItem, on_console_items); + LIST_HEAD(PolicyItem, no_console_items); + Hashmap *user_items; + Hashmap *group_items; +} Policy; + +typedef struct SharedPolicy { + char **configuration; + pthread_mutex_t lock; + pthread_rwlock_t rwlock; + Policy buffer; + Policy *policy; +} SharedPolicy; + +/* policy */ + +int policy_load(Policy *p, char **files); +void policy_free(Policy *p); + +bool policy_check_own(Policy *p, uid_t uid, gid_t gid, const char *name); +bool policy_check_hello(Policy *p, uid_t uid, gid_t gid); +bool policy_check_one_recv(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + const char *name, + const char *path, + const char *interface, + const char *member); +bool policy_check_recv(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + Set *names, + char **namesv, + const char *path, + const char *interface, + const char *member, + bool dbus_to_kernel); +bool policy_check_one_send(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + const char *name, + const char *path, + const char *interface, + const char *member); +bool policy_check_send(Policy *p, + uid_t uid, + gid_t gid, + int message_type, + Set *names, + char **namesv, + const char *path, + const char *interface, + const char *member, + bool dbus_to_kernel, + char **out_used_name); + +void policy_dump(Policy *p); + +const char* policy_item_type_to_string(PolicyItemType t) _const_; +PolicyItemType policy_item_type_from_string(const char *s) _pure_; + +const char* policy_item_class_to_string(PolicyItemClass t) _const_; +PolicyItemClass policy_item_class_from_string(const char *s) _pure_; + +/* shared policy */ + +int shared_policy_new(SharedPolicy **out); +SharedPolicy *shared_policy_free(SharedPolicy *sp); + +int shared_policy_reload(SharedPolicy *sp); +int shared_policy_preload(SharedPolicy *sp, char **configuration); +Policy *shared_policy_acquire(SharedPolicy *sp); +void shared_policy_release(SharedPolicy *sp, Policy *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(SharedPolicy*, shared_policy_free); diff --git a/src/bus-proxyd/driver.c b/src/bus-proxyd/driver.c new file mode 100644 index 0000000000..1af5c310ea --- /dev/null +++ b/src/bus-proxyd/driver.c @@ -0,0 +1,745 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Daniel Mack + Copyright 2014 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stddef.h> +#include <string.h> + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "driver.h" +#include "env-util.h" +#include "proxy.h" +#include "set.h" +#include "strv.h" +#include "synthesize.h" +#include "util.h" + +static int get_creds_by_name(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **_creds, sd_bus_error *error) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + int r; + + assert(bus); + assert(name); + assert(_creds); + + r = sd_bus_get_name_creds(bus, name, mask, &c); + if (r == -ESRCH || r == -ENXIO) + return sd_bus_error_setf(error, SD_BUS_ERROR_NAME_HAS_NO_OWNER, "Name %s is currently not owned by anyone.", name); + if (r < 0) + return r; + + *_creds = c; + c = NULL; + + return 0; +} + +static int get_creds_by_message(sd_bus *bus, sd_bus_message *m, uint64_t mask, sd_bus_creds **_creds, sd_bus_error *error) { + const char *name; + int r; + + assert(bus); + assert(m); + assert(_creds); + + r = sd_bus_message_read(m, "s", &name); + if (r < 0) + return r; + + return get_creds_by_name(bus, name, mask, _creds, error); +} + +static int driver_activation(sd_bus_message *reply, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + ProxyActivation *activation = userdata; + + /* + * The org.freedesktop.DBus.Peer.Ping() call returned. We don't care + * whether this succeeded, failed, was not implemented or timed out. We + * cannot assume that the target reacts to this properly. Hence, just + * send the reply to the activation request and be done. + */ + + m = activation->request; /* claim reference */ + + --activation->proxy->n_activations; + LIST_REMOVE(activations_by_proxy, activation->proxy->activations, activation); + sd_bus_slot_unref(activation->slot); + free(activation); + + return synthetic_reply_method_return(m, "u", BUS_START_REPLY_SUCCESS); +} + +int bus_proxy_process_driver(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m, SharedPolicy *sp, const struct ucred *ucred, Set *owned_names) { + int r; + + assert(a); + assert(b); + assert(m); + + if (!a->is_kernel) + return 0; + + if (!streq_ptr(sd_bus_message_get_destination(m), "org.freedesktop.DBus")) + return 0; + + /* The "Hello()" call is is handled in process_hello() */ + + if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + + if (!sd_bus_message_has_signature(m, "")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + return synthetic_reply_method_return(m, "s", + "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" " + "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n" + "<node>\n" + " <interface name=\"org.freedesktop.DBus.Introspectable\">\n" + " <method name=\"Introspect\">\n" + " <arg name=\"data\" type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " </interface>\n" + " <interface name=\"org.freedesktop.DBus\">\n" + " <method name=\"AddMatch\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"RemoveMatch\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " </method>\n" + " <method name=\"GetConnectionCredentials\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"a{sv}\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"GetConnectionSELinuxSecurityContext\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"ay\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"GetConnectionUnixProcessID\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"u\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"GetConnectionUnixUser\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"u\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"GetId\">\n" + " <arg type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"GetNameOwner\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"Hello\">\n" + " <arg type=\"s\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"ListActivatableNames\">\n" + " <arg type=\"as\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"ListNames\">\n" + " <arg type=\"as\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"ListQueuedOwners\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"as\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"NameHasOwner\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"b\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"ReleaseName\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"u\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"ReloadConfig\">\n" + " </method>\n" + " <method name=\"RequestName\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"u\" direction=\"in\"/>\n" + " <arg type=\"u\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"StartServiceByName\">\n" + " <arg type=\"s\" direction=\"in\"/>\n" + " <arg type=\"u\" direction=\"in\"/>\n" + " <arg type=\"u\" direction=\"out\"/>\n" + " </method>\n" + " <method name=\"UpdateActivationEnvironment\">\n" + " <arg type=\"a{ss}\" direction=\"in\"/>\n" + " </method>\n" + " <signal name=\"NameAcquired\">\n" + " <arg type=\"s\"/>\n" + " </signal>\n" + " <signal name=\"NameLost\">\n" + " <arg type=\"s\"/>\n" + " </signal>\n" + " <signal name=\"NameOwnerChanged\">\n" + " <arg type=\"s\"/>\n" + " <arg type=\"s\"/>\n" + " <arg type=\"s\"/>\n" + " </signal>\n" + " </interface>\n" + "</node>\n"); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "AddMatch")) { + const char *match; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "s", &match); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_add_match(a, NULL, match, proxy_match, p); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_reply_method_return(m, NULL); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "RemoveMatch")) { + const char *match; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "s", &match); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = bus_remove_match_by_string(a, match, NULL, NULL); + if (r == 0) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_MATCH_RULE_NOT_FOUND, "Match rule not found")); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_reply_method_return(m, NULL); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "GetConnectionCredentials")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = get_creds_by_message(a, m, SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SELINUX_CONTEXT, &creds, &error); + if (r < 0) + return synthetic_reply_method_errno(m, r, &error); + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + /* Due to i.e. namespace translations some data might be missing */ + + if (creds->mask & SD_BUS_CREDS_PID) { + r = sd_bus_message_append(reply, "{sv}", "ProcessID", "u", (uint32_t) creds->pid); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + } + + if (creds->mask & SD_BUS_CREDS_EUID) { + r = sd_bus_message_append(reply, "{sv}", "UnixUserID", "u", (uint32_t) creds->euid); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + } + + if (creds->mask & SD_BUS_CREDS_SELINUX_CONTEXT) { + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_append(reply, "s", "LinuxSecurityLabel"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_open_container(reply, 'v', "ay"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_append_array(reply, 'y', creds->label, strlen(creds->label)); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_close_container(reply); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_close_container(reply); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_driver_send(m->bus, reply); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "GetConnectionSELinuxSecurityContext")) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = get_creds_by_message(a, m, SD_BUS_CREDS_SELINUX_CONTEXT, &creds, &error); + if (r < 0) + return synthetic_reply_method_errno(m, r, &error); + + if (!(creds->mask & SD_BUS_CREDS_SELINUX_CONTEXT)) + return synthetic_reply_method_errno(m, -EOPNOTSUPP, NULL); + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_append_array(reply, 'y', creds->label, strlen(creds->label)); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_driver_send(m->bus, reply); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "GetConnectionUnixProcessID")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = get_creds_by_message(a, m, SD_BUS_CREDS_PID, &creds, &error); + if (r < 0) + return synthetic_reply_method_errno(m, r, &error); + + if (!(creds->mask & SD_BUS_CREDS_PID)) + return synthetic_reply_method_errno(m, -EOPNOTSUPP, NULL); + + return synthetic_reply_method_return(m, "u", (uint32_t) creds->pid); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "GetConnectionUnixUser")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = get_creds_by_message(a, m, SD_BUS_CREDS_EUID, &creds, &error); + if (r < 0) + return synthetic_reply_method_errno(m, r, &error); + + if (!(creds->mask & SD_BUS_CREDS_EUID)) + return synthetic_reply_method_errno(m, -EOPNOTSUPP, NULL); + + return synthetic_reply_method_return(m, "u", (uint32_t) creds->euid); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "GetId")) { + sd_id128_t server_id; + char buf[SD_ID128_STRING_MAX]; + + if (!sd_bus_message_has_signature(m, "")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_get_bus_id(a, &server_id); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_reply_method_return(m, "s", sd_id128_to_string(server_id, buf)); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "GetNameOwner")) { + const char *name; + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "s", &name); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + if (streq(name, "org.freedesktop.DBus")) + return synthetic_reply_method_return(m, "s", "org.freedesktop.DBus"); + + r = get_creds_by_name(a, name, SD_BUS_CREDS_UNIQUE_NAME, &creds, &error); + if (r < 0) + return synthetic_reply_method_errno(m, r, &error); + + if (!(creds->mask & SD_BUS_CREDS_UNIQUE_NAME)) + return synthetic_reply_method_errno(m, -EOPNOTSUPP, NULL); + + return synthetic_reply_method_return(m, "s", creds->unique_name); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "ListActivatableNames")) { + _cleanup_strv_free_ char **names = NULL; + + if (!sd_bus_message_has_signature(m, "")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_list_names(a, NULL, &names); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + /* Let's sort the names list to make it stable */ + strv_sort(names); + + return synthetic_reply_method_return_strv(m, names); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "ListNames")) { + _cleanup_strv_free_ char **names = NULL; + + if (!sd_bus_message_has_signature(m, "")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_list_names(a, &names, NULL); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = strv_extend(&names, "org.freedesktop.DBus"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + /* Let's sort the names list to make it stable */ + strv_sort(names); + + return synthetic_reply_method_return_strv(m, names); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "ListQueuedOwners")) { + struct kdbus_cmd_list cmd = { + .flags = KDBUS_LIST_QUEUED, + .size = sizeof(cmd), + }; + struct kdbus_info *name_list, *name; + _cleanup_strv_free_ char **owners = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *arg0; + int err = 0; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "s", &arg0); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_get_name_creds(a, arg0, 0, NULL); + if (r == -ESRCH || r == -ENXIO) { + sd_bus_error_setf(&error, SD_BUS_ERROR_NAME_HAS_NO_OWNER, "Could not get owners of name '%s': no such name.", arg0); + return synthetic_reply_method_errno(m, r, &error); + } + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = ioctl(a->input_fd, KDBUS_CMD_LIST, &cmd); + if (r < 0) + return synthetic_reply_method_errno(m, -errno, NULL); + + name_list = (struct kdbus_info *) ((uint8_t *) a->kdbus_buffer + cmd.offset); + + KDBUS_FOREACH(name, name_list, cmd.list_size) { + struct kdbus_item *item; + char *n; + + KDBUS_ITEM_FOREACH(item, name, items) { + if (item->type == KDBUS_ITEM_OWNED_NAME) { + if (!streq_ptr(item->name.name, arg0)) + continue; + + if (asprintf(&n, ":1.%llu", (unsigned long long) name->id) < 0) { + err = -ENOMEM; + break; + } + + r = strv_consume(&owners, n); + if (r < 0) { + err = r; + break; + } + } + } + + if (err < 0) + break; + } + + r = bus_kernel_cmd_free(a, cmd.offset); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + if (err < 0) + return synthetic_reply_method_errno(m, err, NULL); + + return synthetic_reply_method_return_strv(m, owners); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "NameHasOwner")) { + const char *name; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "s", &name); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + if (streq(name, "org.freedesktop.DBus")) + return synthetic_reply_method_return(m, "b", true); + + r = sd_bus_get_name_creds(a, name, 0, NULL); + if (r < 0 && r != -ESRCH && r != -ENXIO) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_reply_method_return(m, "b", r >= 0); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "ReleaseName")) { + const char *name; + + if (!sd_bus_message_has_signature(m, "s")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "s", &name); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_release_name(a, name); + if (r < 0) { + if (r == -ESRCH) + return synthetic_reply_method_return(m, "u", BUS_NAME_NON_EXISTENT); + if (r == -EADDRINUSE) + return synthetic_reply_method_return(m, "u", BUS_NAME_NOT_OWNER); + + return synthetic_reply_method_errno(m, r, NULL); + } + + set_remove(owned_names, (char*) name); + + return synthetic_reply_method_return(m, "u", BUS_NAME_RELEASED); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "ReloadConfig")) { + if (!sd_bus_message_has_signature(m, "")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = shared_policy_reload(sp); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_reply_method_return(m, NULL); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "RequestName")) { + const char *name; + uint32_t flags, param; + bool in_queue; + + if (!sd_bus_message_has_signature(m, "su")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "su", &name, &flags); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + if (sp) { + Policy *policy; + bool denied; + + policy = shared_policy_acquire(sp); + denied = !policy_check_own(policy, ucred->uid, ucred->gid, name); + shared_policy_release(sp, policy); + if (denied) + return synthetic_reply_method_errno(m, -EPERM, NULL); + } + + if ((flags & ~(BUS_NAME_ALLOW_REPLACEMENT|BUS_NAME_REPLACE_EXISTING|BUS_NAME_DO_NOT_QUEUE)) != 0) + return synthetic_reply_method_errno(m, -EINVAL, NULL); + + param = 0; + if (flags & BUS_NAME_ALLOW_REPLACEMENT) + param |= SD_BUS_NAME_ALLOW_REPLACEMENT; + if (flags & BUS_NAME_REPLACE_EXISTING) + param |= SD_BUS_NAME_REPLACE_EXISTING; + if (!(flags & BUS_NAME_DO_NOT_QUEUE)) + param |= SD_BUS_NAME_QUEUE; + + r = set_put_strdup(owned_names, name); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_request_name(a, name, param); + if (r < 0) { + if (r == -EALREADY) + return synthetic_reply_method_return(m, "u", BUS_NAME_ALREADY_OWNER); + + set_remove(owned_names, (char*) name); + + if (r == -EEXIST) + return synthetic_reply_method_return(m, "u", BUS_NAME_EXISTS); + return synthetic_reply_method_errno(m, r, NULL); + } + + in_queue = (r == 0); + + if (in_queue) + return synthetic_reply_method_return(m, "u", BUS_NAME_IN_QUEUE); + + return synthetic_reply_method_return(m, "u", BUS_NAME_PRIMARY_OWNER); + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "StartServiceByName")) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *msg = NULL; + ProxyActivation *activation; + const char *name; + uint64_t cookie; + uint32_t flags; + + if (!sd_bus_message_has_signature(m, "su")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_read(m, "su", &name, &flags); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + if (flags != 0) + return synthetic_reply_method_errno(m, -EINVAL, NULL); + + r = sd_bus_get_name_creds(a, name, 0, NULL); + if (r >= 0 || streq(name, "org.freedesktop.DBus")) + return synthetic_reply_method_return(m, "u", BUS_START_REPLY_ALREADY_RUNNING); + if (r != -ESRCH) + return synthetic_reply_method_errno(m, r, NULL); + + if (p->n_activations >= PROXY_ACTIVATIONS_MAX) + return synthetic_reply_method_errno(m, -EMFILE, NULL); + + r = sd_bus_message_get_cookie(m, &cookie); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_new_method_call(a, + &msg, + name, + "/", + "org.freedesktop.DBus.Peer", + "Ping"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = bus_message_seal(msg, cookie, BUS_DEFAULT_TIMEOUT); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + activation = new0(ProxyActivation, 1); + if (!activation) + return synthetic_reply_method_errno(m, -ENOMEM, NULL); + + r = sd_bus_call_async(a, + &activation->slot, + msg, + driver_activation, + activation, + 0); + if (r < 0) { + free(activation); + return synthetic_reply_method_errno(m, r, NULL); + } + + activation->proxy = p; + activation->request = sd_bus_message_ref(m); + LIST_PREPEND(activations_by_proxy, p->activations, activation); + ++p->n_activations; + return 1; + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "UpdateActivationEnvironment")) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *msg = NULL; + _cleanup_strv_free_ char **args = NULL; + + if (!sd_bus_message_has_signature(m, "a{ss}")) + return synthetic_reply_method_error(m, &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid parameters")); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{ss}"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "ss")) > 0) { + _cleanup_free_ char *s = NULL; + const char *key; + const char *value; + + r = sd_bus_message_read(m, "ss", &key, &value); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + s = strjoin(key, "=", value, NULL); + if (!s) + return synthetic_reply_method_errno(m, -ENOMEM, NULL); + + if (!env_assignment_is_valid(s)) { + log_warning("UpdateActivationEnvironment() called with invalid assignment, discarding: %s", s); + } else { + r = strv_extend(&args, s); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + if (strv_isempty(args)) /* nothing to do? */ + return synthetic_reply_method_return(m, NULL); + + r = sd_bus_message_new_method_call( + a, + &msg, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetEnvironment"); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_message_append_strv(msg, args); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + r = sd_bus_call(a, msg, 0, NULL, NULL); + if (r < 0) + return synthetic_reply_method_errno(m, r, NULL); + + return synthetic_reply_method_return(m, NULL); + + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + r = sd_bus_error_setf(&error, SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method '%s'.", m->member); + + return synthetic_reply_method_errno(m, r, &error); + } +} diff --git a/src/bus-proxyd/driver.h b/src/bus-proxyd/driver.h new file mode 100644 index 0000000000..1630cdc7b9 --- /dev/null +++ b/src/bus-proxyd/driver.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +#include "bus-xml-policy.h" +#include "proxy.h" + +int bus_proxy_process_driver(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m, SharedPolicy *sp, const struct ucred *ucred, Set *owned_names); diff --git a/src/bus-proxyd/proxy.c b/src/bus-proxyd/proxy.c new file mode 100644 index 0000000000..907d93d4c7 --- /dev/null +++ b/src/bus-proxyd/proxy.c @@ -0,0 +1,953 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Daniel Mack + Copyright 2014 Kay Sievers + Copyright 2014 David Herrmann + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <poll.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> + +#include "sd-bus.h" +#include "sd-daemon.h" + +#include "alloc-util.h" +#include "bus-control.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "bus-xml-policy.h" +#include "driver.h" +#include "fd-util.h" +#include "formats-util.h" +#include "log.h" +#include "proxy.h" +#include "set.h" +#include "strv.h" +#include "synthesize.h" +#include "user-util.h" +#include "util.h" + +static int proxy_create_destination(Proxy *p, const char *destination, const char *local_sec, bool negotiate_fds) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *b = NULL; + int r; + + r = sd_bus_new(&b); + if (r < 0) + return log_error_errno(r, "Failed to allocate bus: %m"); + + r = sd_bus_set_description(b, "sd-proxy"); + if (r < 0) + return log_error_errno(r, "Failed to set bus name: %m"); + + r = sd_bus_set_address(b, destination); + if (r < 0) + return log_error_errno(r, "Failed to set address to connect to: %m"); + + r = sd_bus_negotiate_fds(b, negotiate_fds); + if (r < 0) + return log_error_errno(r, "Failed to set FD negotiation: %m"); + + r = sd_bus_negotiate_creds(b, true, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SELINUX_CONTEXT); + if (r < 0) + return log_error_errno(r, "Failed to set credential negotiation: %m"); + + if (p->local_creds.pid > 0) { + b->fake_pids.pid = p->local_creds.pid; + b->fake_pids_valid = true; + + b->fake_creds.uid = UID_INVALID; + b->fake_creds.euid = p->local_creds.uid; + b->fake_creds.suid = UID_INVALID; + b->fake_creds.fsuid = UID_INVALID; + b->fake_creds.gid = GID_INVALID; + b->fake_creds.egid = p->local_creds.gid; + b->fake_creds.sgid = GID_INVALID; + b->fake_creds.fsgid = GID_INVALID; + b->fake_creds_valid = true; + } + + if (local_sec) { + b->fake_label = strdup(local_sec); + if (!b->fake_label) + return log_oom(); + } + + b->manual_peer_interface = true; + + r = sd_bus_start(b); + if (r < 0) + return log_error_errno(r, "Failed to start bus client: %m"); + + p->destination_bus = b; + b = NULL; + return 0; +} + +static int proxy_create_local(Proxy *p, bool negotiate_fds) { + sd_id128_t server_id; + sd_bus *b; + int r; + + r = sd_bus_new(&b); + if (r < 0) + return log_error_errno(r, "Failed to allocate bus: %m"); + + r = sd_bus_set_fd(b, p->local_in, p->local_out); + if (r < 0) { + sd_bus_unref(b); + return log_error_errno(r, "Failed to set fds: %m"); + } + + /* The fds are now owned by the bus, and we indicate that by + * storing the bus object in the proxy object. */ + p->local_bus = b; + + r = sd_bus_get_bus_id(p->destination_bus, &server_id); + if (r < 0) + return log_error_errno(r, "Failed to get server ID: %m"); + + r = sd_bus_set_server(b, 1, server_id); + if (r < 0) + return log_error_errno(r, "Failed to set server mode: %m"); + + r = sd_bus_negotiate_fds(b, negotiate_fds); + if (r < 0) + return log_error_errno(r, "Failed to set FD negotiation: %m"); + + r = sd_bus_negotiate_creds(b, true, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SELINUX_CONTEXT); + if (r < 0) + return log_error_errno(r, "Failed to set credential negotiation: %m"); + + r = sd_bus_set_anonymous(b, true); + if (r < 0) + return log_error_errno(r, "Failed to set anonymous authentication: %m"); + + b->manual_peer_interface = true; + + r = sd_bus_start(b); + if (r < 0) + return log_error_errno(r, "Failed to start bus client: %m"); + + return 0; +} + +static int proxy_match_synthetic(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Proxy *p = userdata; + + p->synthetic_matched = true; + return 0; /* make sure to continue processing it in further handlers */ +} + +/* + * We always need NameOwnerChanged so we can synthesize NameLost and + * NameAcquired. Furthermore, dbus-1 always passes unicast-signals through, so + * subscribe unconditionally. + */ +static int proxy_prepare_matches(Proxy *p) { + _cleanup_free_ char *match = NULL; + const char *unique; + int r; + + if (!p->destination_bus->is_kernel) + return 0; + + r = sd_bus_get_unique_name(p->destination_bus, &unique); + if (r < 0) + return log_error_errno(r, "Failed to get unique name: %m"); + + match = strjoin("type='signal'," + "sender='org.freedesktop.DBus'," + "path='/org/freedesktop/DBus'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "arg1='", + unique, + "'", + NULL); + if (!match) + return log_oom(); + + r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); + if (r < 0) + return log_error_errno(r, "Failed to add match for NameLost: %m"); + + free(match); + match = strjoin("type='signal'," + "sender='org.freedesktop.DBus'," + "path='/org/freedesktop/DBus'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "arg2='", + unique, + "'", + NULL); + if (!match) + return log_oom(); + + r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); + if (r < 0) + return log_error_errno(r, "Failed to add match for NameAcquired: %m"); + + free(match); + match = strjoin("type='signal'," + "destination='", + unique, + "'", + NULL); + if (!match) + return log_oom(); + + r = sd_bus_add_match(p->destination_bus, NULL, match, proxy_match_synthetic, p); + if (r < 0) + log_error_errno(r, "Failed to add match for directed signals: %m"); + /* FIXME: temporarily ignore error to support older kdbus versions */ + + return 0; +} + +int proxy_new(Proxy **out, int in_fd, int out_fd, const char *destination) { + _cleanup_(proxy_freep) Proxy *p = NULL; + _cleanup_free_ char *local_sec = NULL; + bool is_unix; + int r; + + /* This takes possession/destroys the file descriptors passed + * in even on failure. The caller should hence forget about + * the fds in all cases after calling this function and not + * close them. */ + + p = new0(Proxy, 1); + if (!p) { + safe_close(in_fd); + safe_close(out_fd); + return log_oom(); + } + + p->local_in = in_fd; + p->local_out = out_fd; + + p->owned_names = set_new(&string_hash_ops); + if (!p->owned_names) + return log_oom(); + + is_unix = sd_is_socket(in_fd, AF_UNIX, 0, 0) > 0 && + sd_is_socket(out_fd, AF_UNIX, 0, 0) > 0; + + if (is_unix) { + (void) getpeercred(in_fd, &p->local_creds); + (void) getpeersec(in_fd, &local_sec); + } + + r = proxy_create_destination(p, destination, local_sec, is_unix); + if (r < 0) + return r; + + r = proxy_create_local(p, is_unix); + if (r < 0) + return r; + + r = proxy_prepare_matches(p); + if (r < 0) + return r; + + *out = p; + p = NULL; + + return 0; +} + +Proxy *proxy_free(Proxy *p) { + ProxyActivation *activation; + + if (!p) + return NULL; + + while ((activation = p->activations)) { + LIST_REMOVE(activations_by_proxy, p->activations, activation); + sd_bus_message_unref(activation->request); + sd_bus_slot_unref(activation->slot); + free(activation); + } + + if (p->local_bus) + sd_bus_flush_close_unref(p->local_bus); + else { + safe_close(p->local_in); + if (p->local_out != p->local_in) + safe_close(p->local_out); + } + + sd_bus_flush_close_unref(p->destination_bus); + set_free_free(p->owned_names); + free(p); + + return NULL; +} + +int proxy_set_policy(Proxy *p, SharedPolicy *sp, char **configuration) { + _cleanup_strv_free_ char **strv = NULL; + Policy *policy; + int r; + + assert(p); + assert(sp); + + /* no need to load legacy policy if destination is not kdbus */ + if (!p->destination_bus->is_kernel) + return 0; + + p->policy = sp; + + policy = shared_policy_acquire(sp); + if (policy) { + /* policy already pre-loaded */ + shared_policy_release(sp, policy); + return 0; + } + + if (!configuration) { + const char *scope; + + r = sd_bus_get_scope(p->destination_bus, &scope); + if (r < 0) + return log_error_errno(r, "Couldn't determine bus scope: %m"); + + if (streq(scope, "system")) + strv = strv_new("/usr/share/dbus-1/system.conf", + "/etc/dbus-1/system.conf", + "/usr/share/dbus-1/system.d/", + "/etc/dbus-1/system.d/", + "/etc/dbus-1/system-local.conf", + NULL); + else if (streq(scope, "user")) + strv = strv_new("/usr/share/dbus-1/session.conf", + "/etc/dbus-1/session.conf", + "/usr/share/dbus-1/session.d/", + "/etc/dbus-1/session.d/", + "/etc/dbus-1/session-local.conf", + NULL); + else + return log_error("Unknown scope %s, don't know which policy to load. Refusing.", scope); + + if (!strv) + return log_oom(); + + configuration = strv; + } + + return shared_policy_preload(sp, configuration); +} + +int proxy_hello_policy(Proxy *p, uid_t original_uid) { + Policy *policy; + int r = 0; + + assert(p); + + if (!p->policy) + return 0; + + policy = shared_policy_acquire(p->policy); + + if (p->local_creds.uid == original_uid) + log_debug("Permitting access, since bus owner matches bus client."); + else if (policy_check_hello(policy, p->local_creds.uid, p->local_creds.gid)) + log_debug("Permitting access due to XML policy."); + else + r = log_error_errno(EPERM, "Policy denied connection."); + + shared_policy_release(p->policy, policy); + + return r; +} + +static int proxy_wait(Proxy *p) { + uint64_t timeout_destination, timeout_local, t; + int events_destination, events_local, fd; + struct timespec _ts, *ts; + struct pollfd *pollfd; + int r; + + assert(p); + + fd = sd_bus_get_fd(p->destination_bus); + if (fd < 0) + return log_error_errno(fd, "Failed to get fd: %m"); + + events_destination = sd_bus_get_events(p->destination_bus); + if (events_destination < 0) + return log_error_errno(events_destination, "Failed to get events mask: %m"); + + r = sd_bus_get_timeout(p->destination_bus, &timeout_destination); + if (r < 0) + return log_error_errno(r, "Failed to get timeout: %m"); + + events_local = sd_bus_get_events(p->local_bus); + if (events_local < 0) + return log_error_errno(events_local, "Failed to get events mask: %m"); + + r = sd_bus_get_timeout(p->local_bus, &timeout_local); + if (r < 0) + return log_error_errno(r, "Failed to get timeout: %m"); + + t = timeout_destination; + if (t == (uint64_t) -1 || (timeout_local != (uint64_t) -1 && timeout_local < timeout_destination)) + t = timeout_local; + + if (t == (uint64_t) -1) + ts = NULL; + else { + usec_t nw; + + nw = now(CLOCK_MONOTONIC); + if (t > nw) + t -= nw; + else + t = 0; + + ts = timespec_store(&_ts, t); + } + + pollfd = (struct pollfd[3]) { + { .fd = fd, .events = events_destination, }, + { .fd = p->local_in, .events = events_local & POLLIN, }, + { .fd = p->local_out, .events = events_local & POLLOUT, }, + }; + + r = ppoll(pollfd, 3, ts, NULL); + if (r < 0) + return log_error_errno(errno, "ppoll() failed: %m"); + + return 0; +} + +static int handle_policy_error(sd_bus_message *m, int r) { + if (r == -ESRCH || r == -ENXIO) + return synthetic_reply_method_errorf(m, SD_BUS_ERROR_NAME_HAS_NO_OWNER, "Name %s is currently not owned by anyone.", m->destination); + + return r; +} + +static int process_policy_unlocked(sd_bus *from, sd_bus *to, sd_bus_message *m, Policy *policy, const struct ucred *our_ucred, Set *owned_names) { + int r; + + assert(from); + assert(to); + assert(m); + + if (!policy) + return 0; + + /* + * dbus-1 distinguishes expected and non-expected replies by tracking + * method-calls and timeouts. By default, DENY rules are *NEVER* applied + * on expected replies, unless explicitly specified. But we dont track + * method-calls, thus, we cannot know whether a reply is expected. + * Fortunately, the kdbus forbids non-expected replies, so we can safely + * ignore any policy on those and let the kernel deal with it. + * + * TODO: To be correct, we should only ignore policy-tags that are + * applied on non-expected replies. However, so far we don't parse those + * tags so we let everything pass. I haven't seen a DENY policy tag on + * expected-replies, ever, so don't bother.. + */ + if (m->reply_cookie > 0) + return 0; + + if (from->is_kernel) { + uid_t sender_uid = UID_INVALID; + gid_t sender_gid = GID_INVALID; + char **sender_names = NULL; + + /* Driver messages are always OK */ + if (streq_ptr(m->sender, "org.freedesktop.DBus")) + return 0; + + /* The message came from the kernel, and is sent to our legacy client. */ + (void) sd_bus_creds_get_well_known_names(&m->creds, &sender_names); + + (void) sd_bus_creds_get_euid(&m->creds, &sender_uid); + (void) sd_bus_creds_get_egid(&m->creds, &sender_gid); + + if (sender_uid == UID_INVALID || sender_gid == GID_INVALID) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *sender_creds = NULL; + + /* If the message came from another legacy + * client, then the message creds will be + * missing, simply because on legacy clients + * per-message creds were unknown. In this + * case, query the creds of the peer + * instead. */ + + r = bus_get_name_creds_kdbus(from, m->sender, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID, true, &sender_creds); + if (r < 0) + return handle_policy_error(m, r); + + (void) sd_bus_creds_get_euid(sender_creds, &sender_uid); + (void) sd_bus_creds_get_egid(sender_creds, &sender_gid); + } + + /* First check whether the sender can send the message to our name */ + if (policy_check_send(policy, sender_uid, sender_gid, m->header->type, owned_names, NULL, m->path, m->interface, m->member, false, NULL) && + policy_check_recv(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, sender_names, m->path, m->interface, m->member, false)) + return 0; + + /* Return an error back to the caller */ + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + return synthetic_reply_method_errorf(m, SD_BUS_ERROR_ACCESS_DENIED, "Access prohibited by XML receiver policy."); + + /* Return 1, indicating that the message shall not be processed any further */ + return 1; + } + + if (to->is_kernel) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *destination_creds = NULL; + uid_t destination_uid = UID_INVALID; + gid_t destination_gid = GID_INVALID; + const char *destination_unique = NULL; + char **destination_names = NULL; + char *n; + + /* Driver messages are always OK */ + if (streq_ptr(m->destination, "org.freedesktop.DBus")) + return 0; + + /* The message came from the legacy client, and is sent to kdbus. */ + if (m->destination) { + r = bus_get_name_creds_kdbus(to, m->destination, + SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME| + SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID, + true, &destination_creds); + if (r < 0) + return handle_policy_error(m, r); + + r = sd_bus_creds_get_unique_name(destination_creds, &destination_unique); + if (r < 0) + return handle_policy_error(m, r); + + (void) sd_bus_creds_get_well_known_names(destination_creds, &destination_names); + + (void) sd_bus_creds_get_euid(destination_creds, &destination_uid); + (void) sd_bus_creds_get_egid(destination_creds, &destination_gid); + } + + /* First check if we (the sender) can send to this name */ + if (sd_bus_message_is_signal(m, NULL, NULL)) { + /* If we forward a signal from dbus-1 to kdbus, we have + * no idea who the recipient is. Therefore, we cannot + * apply any dbus-1 policies that match on receiver + * credentials. We know sd-bus always sets + * KDBUS_MSG_SIGNAL, so the kernel applies policies to + * the message. Therefore, skip policy checks in this + * case. */ + return 0; + } else if (policy_check_send(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, destination_names, m->path, m->interface, m->member, true, &n)) { + if (n) { + /* If we made a receiver decision, then remember which + * name's policy we used, and to which unique ID it + * mapped when we made the decision. Then, let's pass + * this to the kernel when sending the message, so that + * it refuses the operation should the name and unique + * ID not map to each other anymore. */ + + r = free_and_strdup(&m->destination_ptr, n); + if (r < 0) + return r; + + r = bus_kernel_parse_unique_name(destination_unique, &m->verify_destination_id); + if (r < 0) + return r; + } + + if (policy_check_recv(policy, destination_uid, destination_gid, m->header->type, owned_names, NULL, m->path, m->interface, m->member, true)) + return 0; + } + + /* Return an error back to the caller */ + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + return synthetic_reply_method_errorf(m, SD_BUS_ERROR_ACCESS_DENIED, "Access prohibited by XML sender policy."); + + /* Return 1, indicating that the message shall not be processed any further */ + return 1; + } + + return 0; +} + +static int process_policy(sd_bus *from, sd_bus *to, sd_bus_message *m, SharedPolicy *sp, const struct ucred *our_ucred, Set *owned_names) { + Policy *policy; + int r; + + assert(sp); + + policy = shared_policy_acquire(sp); + r = process_policy_unlocked(from, to, m, policy, our_ucred, owned_names); + shared_policy_release(sp, policy); + + return r; +} + +static int process_hello(Proxy *p, sd_bus_message *m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL; + bool is_hello; + int r; + + assert(p); + assert(m); + + /* As reaction to hello we need to respond with two messages: + * the callback reply and the NameAcquired for the unique + * name, since hello is otherwise obsolete on kdbus. */ + + is_hello = + sd_bus_message_is_method_call(m, "org.freedesktop.DBus", "Hello") && + streq_ptr(m->destination, "org.freedesktop.DBus"); + + if (!is_hello) { + if (p->got_hello) + return 0; + + return log_error_errno(EIO, "First packet isn't hello (it's %s.%s), aborting.", m->interface, m->member); + } + + if (p->got_hello) + return log_error_errno(EIO, "Got duplicate hello, aborting."); + + p->got_hello = true; + + if (!p->destination_bus->is_kernel) + return 0; + + r = sd_bus_message_new_method_return(m, &n); + if (r < 0) + return log_error_errno(r, "Failed to generate HELLO reply: %m"); + + r = sd_bus_message_append(n, "s", p->destination_bus->unique_name); + if (r < 0) + return log_error_errno(r, "Failed to append unique name to HELLO reply: %m"); + + r = bus_message_append_sender(n, "org.freedesktop.DBus"); + if (r < 0) + return log_error_errno(r, "Failed to append sender to HELLO reply: %m"); + + r = bus_seal_synthetic_message(p->local_bus, n); + if (r < 0) + return log_error_errno(r, "Failed to seal HELLO reply: %m"); + + r = sd_bus_send(p->local_bus, n, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send HELLO reply: %m"); + + n = sd_bus_message_unref(n); + r = sd_bus_message_new_signal( + p->local_bus, + &n, + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameAcquired"); + if (r < 0) + return log_error_errno(r, "Failed to allocate initial NameAcquired message: %m"); + + r = sd_bus_message_append(n, "s", p->destination_bus->unique_name); + if (r < 0) + return log_error_errno(r, "Failed to append unique name to NameAcquired message: %m"); + + r = bus_message_append_sender(n, "org.freedesktop.DBus"); + if (r < 0) + return log_error_errno(r, "Failed to append sender to NameAcquired message: %m"); + + r = sd_bus_message_set_destination(n, p->destination_bus->unique_name); + if (r < 0) + return log_error_errno(r, "Failed to set destination for NameAcquired message: %m"); + + r = bus_seal_synthetic_message(p->local_bus, n); + if (r < 0) + return log_error_errno(r, "Failed to seal NameAcquired message: %m"); + + r = sd_bus_send(p->local_bus, n, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send NameAcquired message: %m"); + + return 1; +} + +static int patch_sender(sd_bus *a, sd_bus_message *m) { + char **well_known = NULL; + sd_bus_creds *c; + int r; + + assert(a); + assert(m); + + if (!a->is_kernel) + return 0; + + /* We will change the sender of messages from the bus driver + * so that they originate from the bus driver. This is a + * speciality originating from dbus1, where the bus driver did + * not have a unique id, but only the well-known name. */ + + c = sd_bus_message_get_creds(m); + if (!c) + return 0; + + r = sd_bus_creds_get_well_known_names(c, &well_known); + if (r < 0) + return r; + + if (strv_contains(well_known, "org.freedesktop.DBus")) + m->sender = "org.freedesktop.DBus"; + + return 0; +} + +static int proxy_process_destination_to_local(Proxy *p) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + bool matched, matched_synthetic; + int r; + + assert(p); + + /* + * Usually, we would just take any message that the bus passes to us + * and forward it to the local connection. However, there are actually + * applications that fail if they receive broadcasts that they didn't + * subscribe to. Therefore, we actually emulate a real broadcast + * matching here, and discard any broadcasts that weren't matched. Our + * match-handlers remembers whether a message was matched by any rule, + * by marking it in @p->message_matched. + */ + + r = sd_bus_process(p->destination_bus, &m); + + matched = p->message_matched; + matched_synthetic = p->synthetic_matched; + p->message_matched = false; + p->synthetic_matched = false; + + if (r == -ECONNRESET || r == -ENOTCONN) /* Treat 'connection reset by peer' as clean exit condition */ + return r; + if (r < 0) { + log_error_errno(r, "Failed to process destination bus: %m"); + return r; + } + if (r == 0) + return 0; + if (!m) + return 1; + + /* We officially got EOF, let's quit */ + if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) + return -ECONNRESET; + + r = synthesize_name_acquired(p, p->destination_bus, p->local_bus, m); + if (r == -ECONNRESET || r == -ENOTCONN) + return r; + if (r < 0) + return log_error_errno(r, "Failed to synthesize message: %m"); + + /* discard broadcasts that were not matched by any MATCH rule */ + if (!matched && !sd_bus_message_get_destination(m)) { + if (!matched_synthetic) + log_debug("Dropped unmatched broadcast: uid=" UID_FMT " gid=" GID_FMT " pid=" PID_FMT " message=%s path=%s interface=%s member=%s sender=%s destination=%s", + p->local_creds.uid, p->local_creds.gid, p->local_creds.pid, bus_message_type_to_string(m->header->type), + strna(m->path), strna(m->interface), strna(m->member), strna(m->sender), strna(m->destination)); + return 1; + } + + patch_sender(p->destination_bus, m); + + if (p->policy) { + r = process_policy(p->destination_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); + if (r == -ECONNRESET || r == -ENOTCONN) + return r; + if (r < 0) + return log_error_errno(r, "Failed to process policy: %m"); + if (r > 0) + return 1; + } + + r = sd_bus_send(p->local_bus, m, NULL); + if (r < 0) { + if (r == -ECONNRESET || r == -ENOTCONN) + return r; + + /* If the peer tries to send a reply and it is + * rejected with EBADSLT by the kernel, we ignore the + * error. This catches cases where the original + * method-call didn't had EXPECT_REPLY set, but the + * proxy-peer still sends a reply. This is allowed in + * dbus1, but not in kdbus. We don't want to track + * reply-windows in the proxy, so we simply ignore + * EBADSLT for all replies. The only downside is, that + * callers are no longer notified if their replies are + * dropped. However, this is equivalent to the + * caller's timeout to expire, so this should be + * acceptable. Nobody sane sends replies without a + * matching method-call, so nobody should care. */ + + /* FIXME: remove -EPERM when kdbus is updated */ + if ((r == -EPERM || r == -EBADSLT) && m->reply_cookie > 0) + return 1; + + /* Return the error to the client, if we can */ + synthetic_reply_method_errnof(m, r, "Failed to forward message we got from destination: %m"); + if (r == -ENOBUFS) { + /* if local dbus1 peer does not dispatch its queue, warn only once */ + if (!p->queue_overflow) + log_error("Dropped messages due to queue overflow of local peer (pid: "PID_FMT" uid: "UID_FMT")", p->local_creds.pid, p->local_creds.uid); + p->queue_overflow = true; + } else + log_error_errno(r, + "Failed to forward message we got from destination: uid=" UID_FMT " gid=" GID_FMT" message=%s destination=%s path=%s interface=%s member=%s: %m", + p->local_creds.uid, p->local_creds.gid, bus_message_type_to_string(m->header->type), + strna(m->destination), strna(m->path), strna(m->interface), strna(m->member)); + + return 1; + } + + p->queue_overflow = false; + return 1; +} + +static int proxy_process_local_to_destination(Proxy *p) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(p); + + r = sd_bus_process(p->local_bus, &m); + if (r == -ECONNRESET || r == -ENOTCONN) /* Treat 'connection reset by peer' as clean exit condition */ + return r; + if (r < 0) { + log_error_errno(r, "Failed to process local bus: %m"); + return r; + } + if (r == 0) + return 0; + if (!m) + return 1; + + /* We officially got EOF, let's quit */ + if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) + return -ECONNRESET; + + r = process_hello(p, m); + if (r == -ECONNRESET || r == -ENOTCONN) + return r; + if (r < 0) + return log_error_errno(r, "Failed to process HELLO: %m"); + if (r > 0) + return 1; + + r = bus_proxy_process_driver(p, p->destination_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); + if (r == -ECONNRESET || r == -ENOTCONN) + return r; + if (r < 0) + return log_error_errno(r, "Failed to process driver calls: %m"); + if (r > 0) + return 1; + + for (;;) { + if (p->policy) { + r = process_policy(p->local_bus, p->destination_bus, m, p->policy, &p->local_creds, p->owned_names); + if (r == -ECONNRESET || r == -ENOTCONN) + return r; + if (r < 0) + return log_error_errno(r, "Failed to process policy: %m"); + if (r > 0) + return 1; + } + + r = sd_bus_send(p->destination_bus, m, NULL); + if (r < 0) { + if (r == -ECONNRESET || r == -ENOTCONN) + return r; + + /* The name database changed since the policy check, hence let's check again */ + if (r == -EREMCHG) + continue; + + /* see above why EBADSLT is ignored for replies */ + if ((r == -EPERM || r == -EBADSLT) && m->reply_cookie > 0) + return 1; + + synthetic_reply_method_errnof(m, r, "Failed to forward message we got from local: %m"); + log_error_errno(r, + "Failed to forward message we got from local: uid=" UID_FMT " gid=" GID_FMT" message=%s destination=%s path=%s interface=%s member=%s: %m", + p->local_creds.uid, p->local_creds.gid, bus_message_type_to_string(m->header->type), + strna(m->destination), strna(m->path), strna(m->interface), strna(m->member)); + return 1; + } + + break; + } + + return 1; +} + +int proxy_match(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Proxy *p = userdata; + + p->message_matched = true; + return 0; /* make sure to continue processing it in further handlers */ +} + +int proxy_run(Proxy *p) { + int r; + + assert(p); + + for (;;) { + bool busy = false; + + if (p->got_hello) { + /* Read messages from bus, to pass them on to our client */ + r = proxy_process_destination_to_local(p); + if (r == -ECONNRESET || r == -ENOTCONN) + return 0; + if (r < 0) + return r; + if (r > 0) + busy = true; + } + + /* Read messages from our client, to pass them on to the bus */ + r = proxy_process_local_to_destination(p); + if (r == -ECONNRESET || r == -ENOTCONN) + return 0; + if (r < 0) + return r; + if (r > 0) + busy = true; + + if (!busy) { + r = proxy_wait(p); + if (r == -ECONNRESET || r == -ENOTCONN) + return 0; + if (r < 0) + return r; + } + } + + return 0; +} diff --git a/src/bus-proxyd/proxy.h b/src/bus-proxyd/proxy.h new file mode 100644 index 0000000000..d9e75cf73b --- /dev/null +++ b/src/bus-proxyd/proxy.h @@ -0,0 +1,66 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 David Herrmann + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +#include "bus-xml-policy.h" + +typedef struct Proxy Proxy; +typedef struct ProxyActivation ProxyActivation; + +#define PROXY_ACTIVATIONS_MAX (16) /* max parallel activation requests */ + +struct Proxy { + sd_bus *local_bus; + struct ucred local_creds; + int local_in; + int local_out; + + sd_bus *destination_bus; + + Set *owned_names; + SharedPolicy *policy; + + LIST_HEAD(ProxyActivation, activations); + size_t n_activations; + + bool got_hello : 1; + bool queue_overflow : 1; + bool message_matched : 1; + bool synthetic_matched : 1; +}; + +struct ProxyActivation { + LIST_FIELDS(ProxyActivation, activations_by_proxy); + Proxy *proxy; + sd_bus_message *request; + sd_bus_slot *slot; +}; + +int proxy_new(Proxy **out, int in_fd, int out_fd, const char *dest); +Proxy *proxy_free(Proxy *p); + +int proxy_set_policy(Proxy *p, SharedPolicy *policy, char **configuration); +int proxy_hello_policy(Proxy *p, uid_t original_uid); +int proxy_match(sd_bus_message *m, void *userdata, sd_bus_error *error); +int proxy_run(Proxy *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Proxy*, proxy_free); diff --git a/src/bus-proxyd/stdio-bridge.c b/src/bus-proxyd/stdio-bridge.c new file mode 100644 index 0000000000..291c1b09e3 --- /dev/null +++ b/src/bus-proxyd/stdio-bridge.c @@ -0,0 +1,244 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Daniel Mack + Copyright 2014 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <getopt.h> +#include <stddef.h> +#include <string.h> +#include <unistd.h> + +#include "sd-bus.h" +#include "sd-daemon.h" + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "def.h" +#include "formats-util.h" +#include "log.h" +#include "proxy.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +static char *arg_address = NULL; +static char *arg_command_line_buffer = NULL; + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "Connect STDIO to a given bus address.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --machine=MACHINE Connect to specified machine\n" + " --address=ADDRESS Connect to the bus specified by ADDRESS\n" + " (default: " DEFAULT_SYSTEM_BUS_ADDRESS ")\n", + program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_ADDRESS, + ARG_MACHINE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "address", required_argument, NULL, ARG_ADDRESS }, + { "machine", required_argument, NULL, ARG_MACHINE }, + {}, + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_ADDRESS: { + char *a; + + a = strdup(optarg); + if (!a) + return log_oom(); + + free(arg_address); + arg_address = a; + break; + } + + case ARG_MACHINE: { + _cleanup_free_ char *e = NULL; + char *a; + + e = bus_address_escape(optarg); + if (!e) + return log_oom(); + + a = strjoin("x-machine-kernel:machine=", e, ";x-machine-unix:machine=", e, NULL); + if (!a) + return log_oom(); + + free(arg_address); + arg_address = a; + + break; + } + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + /* If the first command line argument is only "x" characters + * we'll write who we are talking to into it, so that "ps" is + * explanatory */ + arg_command_line_buffer = argv[optind]; + if (argc > optind + 1 || (arg_command_line_buffer && !in_charset(arg_command_line_buffer, "x"))) { + log_error("Too many arguments"); + return -EINVAL; + } + + if (!arg_address) { + arg_address = strdup(DEFAULT_SYSTEM_BUS_ADDRESS); + if (!arg_address) + return log_oom(); + } + + return 1; +} + +static int rename_service(sd_bus *a, sd_bus *b) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_free_ char *p = NULL, *name = NULL; + const char *comm; + char **cmdline; + uid_t uid; + pid_t pid; + int r; + + assert(a); + assert(b); + + r = sd_bus_get_owner_creds(b, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_AUGMENT, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &uid); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + r = sd_bus_creds_get_cmdline(creds, &cmdline); + if (r < 0) + return r; + + r = sd_bus_creds_get_comm(creds, &comm); + if (r < 0) + return r; + + name = uid_to_name(uid); + if (!name) + return -ENOMEM; + + p = strv_join(cmdline, " "); + if (!p) + return -ENOMEM; + + /* The status string gets the full command line ... */ + sd_notifyf(false, + "STATUS=Processing requests from client PID "PID_FMT" (%s); UID "UID_FMT" (%s)", + pid, p, + uid, name); + + /* ... and the argv line only the short comm */ + if (arg_command_line_buffer) { + size_t m, w; + + m = strlen(arg_command_line_buffer); + w = snprintf(arg_command_line_buffer, m, + "[PID "PID_FMT"/%s; UID "UID_FMT"/%s]", + pid, comm, + uid, name); + + if (m > w) + memzero(arg_command_line_buffer + w, m - w); + } + + log_debug("Running on behalf of PID "PID_FMT" (%s), UID "UID_FMT" (%s), %s", + pid, p, + uid, name, + a->unique_name); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(proxy_freep) Proxy *p = NULL; + int r; + + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = proxy_new(&p, STDIN_FILENO, STDOUT_FILENO, arg_address); + if (r < 0) + goto finish; + + r = rename_service(p->destination_bus, p->local_bus); + if (r < 0) + log_debug_errno(r, "Failed to rename process: %m"); + + r = proxy_run(p); + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down."); + + free(arg_address); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/bus-proxyd/synthesize.c b/src/bus-proxyd/synthesize.c new file mode 100644 index 0000000000..8eea7dc5b9 --- /dev/null +++ b/src/bus-proxyd/synthesize.c @@ -0,0 +1,225 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Daniel Mack + Copyright 2014 Kay Sievers + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stddef.h> + +#include "sd-bus.h" + +#include "bus-internal.h" +#include "bus-match.h" +#include "bus-message.h" +#include "bus-util.h" +#include "synthesize.h" +#include "util.h" + +int synthetic_driver_send(sd_bus *b, sd_bus_message *m) { + int r; + + assert(b); + assert(m); + + r = bus_message_append_sender(m, "org.freedesktop.DBus"); + if (r < 0) + return r; + + r = bus_seal_synthetic_message(b, m); + if (r < 0) + return r; + + return sd_bus_send(b, m, NULL); +} + +int synthetic_reply_method_error(sd_bus_message *call, const sd_bus_error *e) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(call); + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_error(call, &m, e); + if (r < 0) + return r; + + return synthetic_driver_send(call->bus, m); +} + +int synthetic_reply_method_errorf(sd_bus_message *call, const char *name, const char *format, ...) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + va_list ap; + + va_start(ap, format); + bus_error_setfv(&error, name, format, ap); + va_end(ap); + + return synthetic_reply_method_error(call, &error); +} + +int synthetic_reply_method_errno(sd_bus_message *call, int error, const sd_bus_error *p) { + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + + assert(call); + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + if (sd_bus_error_is_set(p)) + return synthetic_reply_method_error(call, p); + + sd_bus_error_set_errno(&berror, error); + + return synthetic_reply_method_error(call, &berror); +} + +int synthetic_reply_method_errnof(sd_bus_message *call, int error, const char *format, ...) { + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + va_list ap; + + assert(call); + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + va_start(ap, format); + sd_bus_error_set_errnofv(&berror, error, format, ap); + va_end(ap); + + return synthetic_reply_method_error(call, &berror); +} + +int synthetic_reply_method_return(sd_bus_message *call, const char *types, ...) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(call); + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_return(call, &m); + if (r < 0) + return r; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + } + + return synthetic_driver_send(call->bus, m); +} + +int synthetic_reply_method_return_strv(sd_bus_message *call, char **l) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(call); + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_return(call, &m); + if (r < 0) + return synthetic_reply_method_errno(call, r, NULL); + + r = sd_bus_message_append_strv(m, l); + if (r < 0) + return synthetic_reply_method_errno(call, r, NULL); + + return synthetic_driver_send(call->bus, m); +} + +int synthesize_name_acquired(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL; + const char *name, *old_owner, *new_owner; + int r; + + assert(p); + assert(a); + assert(b); + assert(m); + + /* If we get NameOwnerChanged for our own name, we need to + * synthesize NameLost/NameAcquired, since socket clients need + * that, even though it is obsoleted on kdbus */ + + if (!a->is_kernel) + return 0; + + if (!sd_bus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged") || + !streq_ptr(m->path, "/org/freedesktop/DBus") || + !streq_ptr(m->sender, "org.freedesktop.DBus")) + return 0; + + r = sd_bus_message_read(m, "sss", &name, &old_owner, &new_owner); + if (r < 0) + return r; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + if (streq(old_owner, a->unique_name)) { + + r = sd_bus_message_new_signal( + b, + &n, + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameLost"); + + } else if (streq(new_owner, a->unique_name)) { + + r = sd_bus_message_new_signal( + b, + &n, + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameAcquired"); + } else + return 0; + + if (r < 0) + return r; + + r = sd_bus_message_append(n, "s", name); + if (r < 0) + return r; + + r = bus_message_append_sender(n, "org.freedesktop.DBus"); + if (r < 0) + return r; + + r = sd_bus_message_set_destination(n, a->unique_name); + if (r < 0) + return r; + + r = bus_seal_synthetic_message(b, n); + if (r < 0) + return r; + + return sd_bus_send(b, n, NULL); +} diff --git a/src/bus-proxyd/synthesize.h b/src/bus-proxyd/synthesize.h new file mode 100644 index 0000000000..1b7197f8ec --- /dev/null +++ b/src/bus-proxyd/synthesize.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "sd-bus.h" + +#include "proxy.h" + +int synthetic_driver_send(sd_bus *b, sd_bus_message *m); + +int synthetic_reply_method_return(sd_bus_message *call, const char *types, ...); +int synthetic_reply_method_return_strv(sd_bus_message *call, char **l); + +int synthetic_reply_method_error(sd_bus_message *call, const sd_bus_error *e); +int synthetic_reply_method_errorf(sd_bus_message *call, const char *name, const char *format, ...) _sd_printf_(3, 4); +int synthetic_reply_method_errno(sd_bus_message *call, int error, const sd_bus_error *p); +int synthetic_reply_method_errnof(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); + +int synthesize_name_acquired(Proxy *p, sd_bus *a, sd_bus *b, sd_bus_message *m); diff --git a/src/bus-proxyd/test-bus-xml-policy.c b/src/bus-proxyd/test-bus-xml-policy.c new file mode 100644 index 0000000000..af7c9128a2 --- /dev/null +++ b/src/bus-proxyd/test-bus-xml-policy.c @@ -0,0 +1,170 @@ +/*** + This file is part of systemd. + + Copyright 2014 Daniel Mack + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stddef.h> +#include <unistd.h> + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "bus-xml-policy.h" +#include "log.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static int test_policy_load(Policy *p, const char *name) { + _cleanup_free_ char *path = NULL; + int r = 0; + + path = strjoin(TEST_DIR, "/bus-policy/", name, NULL); + assert_se(path); + + if (access(path, R_OK) == 0) + r = policy_load(p, STRV_MAKE(path)); + else + r = -ENOENT; + + return r; +} + +static int show_policy(const char *fn) { + Policy p = {}; + int r; + + r = policy_load(&p, STRV_MAKE(fn)); + if (r < 0) { + log_error_errno(r, "Failed to load policy %s: %m", fn); + return r; + } + + policy_dump(&p); + policy_free(&p); + + return 0; +} + +int main(int argc, char *argv[]) { + + Policy p = {}; + + printf("Showing session policy BEGIN\n"); + show_policy("/etc/dbus-1/session.conf"); + printf("Showing session policy END\n"); + + printf("Showing system policy BEGIN\n"); + show_policy("/etc/dbus-1/system.conf"); + printf("Showing system policy END\n"); + + /* Ownership tests */ + assert_se(test_policy_load(&p, "ownerships.conf") == 0); + + assert_se(policy_check_own(&p, 0, 0, "org.test.test1") == true); + assert_se(policy_check_own(&p, 1, 0, "org.test.test1") == true); + + assert_se(policy_check_own(&p, 0, 0, "org.test.test2") == true); + assert_se(policy_check_own(&p, 1, 0, "org.test.test2") == false); + + assert_se(policy_check_own(&p, 0, 0, "org.test.test3") == false); + assert_se(policy_check_own(&p, 1, 0, "org.test.test3") == false); + + assert_se(policy_check_own(&p, 0, 0, "org.test.test4") == false); + assert_se(policy_check_own(&p, 1, 0, "org.test.test4") == true); + + policy_free(&p); + + /* Signaltest */ + assert_se(test_policy_load(&p, "signals.conf") == 0); + + assert_se(policy_check_one_send(&p, 0, 0, SD_BUS_MESSAGE_SIGNAL, "bli.bla.blubb", NULL, "/an/object/path", NULL) == true); + assert_se(policy_check_one_send(&p, 1, 0, SD_BUS_MESSAGE_SIGNAL, "bli.bla.blubb", NULL, "/an/object/path", NULL) == false); + + policy_free(&p); + + /* Method calls */ + assert_se(test_policy_load(&p, "methods.conf") == 0); + policy_dump(&p); + + assert_se(policy_check_one_send(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "bli.bla.blubb", "Member") == false); + assert_se(policy_check_one_send(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "bli.bla.blubb", "Member") == false); + assert_se(policy_check_one_send(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "org.test.int1", "Member") == true); + assert_se(policy_check_one_send(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "org.test.int2", "Member") == true); + + assert_se(policy_check_one_recv(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test3", "/an/object/path", "org.test.int3", "Member111") == true); + + policy_free(&p); + + /* User and groups */ + assert_se(test_policy_load(&p, "hello.conf") == 0); + policy_dump(&p); + + assert_se(policy_check_hello(&p, 0, 0) == true); + assert_se(policy_check_hello(&p, 1, 0) == false); + assert_se(policy_check_hello(&p, 0, 1) == false); + + policy_free(&p); + + /* dbus1 test file: ownership */ + + assert_se(test_policy_load(&p, "check-own-rules.conf") >= 0); + policy_dump(&p); + + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop") == false); + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop.ManySystem") == false); + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop.ManySystems") == true); + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop.ManySystems.foo") == true); + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop.ManySystems.foo.bar") == true); + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop.ManySystems2") == false); + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop.ManySystems2.foo") == false); + assert_se(policy_check_own(&p, 0, 0, "org.freedesktop.ManySystems2.foo.bar") == false); + + policy_free(&p); + + /* dbus1 test file: many rules */ + + assert_se(test_policy_load(&p, "many-rules.conf") >= 0); + policy_dump(&p); + policy_free(&p); + + /* dbus1 test file: generic test */ + + assert_se(test_policy_load(&p, "test.conf") >= 0); + policy_dump(&p); + + assert_se(policy_check_own(&p, 0, 0, "org.foo.FooService") == true); + assert_se(policy_check_own(&p, 0, 0, "org.foo.FooService2") == false); + assert_se(policy_check_one_send(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "org.test.int2", "Member") == false); + assert_se(policy_check_one_send(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "org.foo.FooBroadcastInterface", "Member") == true); + assert_se(policy_check_one_recv(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.foo.FooService", "/an/object/path", "org.foo.FooBroadcastInterface", "Member") == true); + assert_se(policy_check_one_recv(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.foo.FooService", "/an/object/path", "org.foo.FooBroadcastInterface2", "Member") == false); + assert_se(policy_check_one_recv(&p, 0, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.foo.FooService2", "/an/object/path", "org.foo.FooBroadcastInterface", "Member") == false); + + assert_se(policy_check_own(&p, 100, 0, "org.foo.FooService") == false); + assert_se(policy_check_own(&p, 100, 0, "org.foo.FooService2") == false); + assert_se(policy_check_one_send(&p, 100, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "org.test.int2", "Member") == false); + assert_se(policy_check_one_send(&p, 100, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.test.test1", "/an/object/path", "org.foo.FooBroadcastInterface", "Member") == false); + assert_se(policy_check_one_recv(&p, 100, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.foo.FooService", "/an/object/path", "org.foo.FooBroadcastInterface", "Member") == true); + assert_se(policy_check_one_recv(&p, 100, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.foo.FooService", "/an/object/path", "org.foo.FooBroadcastInterface2", "Member") == false); + assert_se(policy_check_one_recv(&p, 100, 0, SD_BUS_MESSAGE_METHOD_CALL, "org.foo.FooService2", "/an/object/path", "org.foo.FooBroadcastInterface", "Member") == false); + + policy_free(&p); + + return EXIT_SUCCESS; +} |