diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2015-01-16 14:19:19 +0100 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2015-01-17 11:55:14 +0100 |
commit | c0395aeb903cde25bd9e81fba3334f63335fe0ef (patch) | |
tree | a3f1ba19b4d4660f77bfb9c6d3cb449454fc933f /src | |
parent | 91fd0103cc1525c66e3efb03ef781e9553437724 (diff) |
bus-proxy: extract proxy into Proxy object
Move all the proxy code into a "struct Proxy" object that can be used
from multiple binaries.
We now dropped SMACK as we have to refactor it to work properly. We can
introduce it later on.
Diffstat (limited to 'src')
-rw-r--r-- | src/bus-proxyd/bus-proxyd.c | 812 | ||||
-rw-r--r-- | src/bus-proxyd/proxy.c | 859 | ||||
-rw-r--r-- | src/bus-proxyd/proxy.h | 53 |
3 files changed, 920 insertions, 804 deletions
diff --git a/src/bus-proxyd/bus-proxyd.c b/src/bus-proxyd/bus-proxyd.c index b6933417a1..debaab2a2e 100644 --- a/src/bus-proxyd/bus-proxyd.c +++ b/src/bus-proxyd/bus-proxyd.c @@ -49,6 +49,7 @@ #include "set.h" #include "bus-xml-policy.h" #include "driver.h" +#include "proxy.h" #include "synthesize.h" static char *arg_address = NULL; @@ -247,457 +248,9 @@ static int rename_service(sd_bus *a, sd_bus *b) { 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(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; - bool granted = false; - - /* 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. */ - sd_bus_creds_get_well_known_names(&m->creds, &sender_names); - - (void) sd_bus_creds_get_uid(&m->creds, &sender_uid); - (void) sd_bus_creds_get_gid(&m->creds, &sender_gid); - - if (sender_uid == UID_INVALID || sender_gid == GID_INVALID) { - _cleanup_bus_creds_unref_ 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_UID|SD_BUS_CREDS_GID, true, &sender_creds); - if (r < 0) - return handle_policy_error(m, r); - - (void) sd_bus_creds_get_uid(sender_creds, &sender_uid); - (void) sd_bus_creds_get_gid(sender_creds, &sender_gid); - } - - /* First check whether the sender can send the message to our name */ - if (set_isempty(owned_names)) { - if (policy_check_send(policy, sender_uid, sender_gid, m->header->type, NULL, m->path, m->interface, m->member, false)) - granted = true; - } else { - Iterator i; - char *n; - - SET_FOREACH(n, owned_names, i) - if (policy_check_send(policy, sender_uid, sender_gid, m->header->type, n, m->path, m->interface, m->member, false)) { - granted = true; - break; - } - } - - if (granted) { - /* Then check whether us (the recipient) can receive from the sender's name */ - if (strv_isempty(sender_names)) { - if (policy_check_recv(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, m->path, m->interface, m->member, false)) - return 0; - } else { - char **n; - - STRV_FOREACH(n, sender_names) { - if (policy_check_recv(policy, our_ucred->uid, our_ucred->gid, m->header->type, *n, 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_bus_creds_unref_ 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; - bool granted = false; - - /* 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_UID|SD_BUS_CREDS_GID|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); - - sd_bus_creds_get_well_known_names(destination_creds, &destination_names); - - (void) sd_bus_creds_get_uid(destination_creds, &destination_uid); - (void) sd_bus_creds_get_gid(destination_creds, &destination_gid); - } - - /* First check if we (the sender) can send to this name */ - if (strv_isempty(destination_names)) { - if (policy_check_send(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, m->path, m->interface, m->member, true)) - granted = true; - } else { - char **n; - - STRV_FOREACH(n, destination_names) { - if (policy_check_send(policy, our_ucred->uid, our_ucred->gid, m->header->type, *n, m->path, m->interface, m->member, true)) { - - /* 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) - break; - - granted = true; - break; - } - } - } - - /* Then check if the recipient can receive from our name */ - if (granted) { - 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 - * receiver policies that match on receiver - * credentials. We know sd-bus always sets - * KDBUS_MSG_SIGNAL, so the kernel applies - * receiver policies to the message. Therefore, - * skip policy checks in this case. */ - return 0; - } else if (set_isempty(owned_names)) { - if (policy_check_recv(policy, destination_uid, destination_gid, m->header->type, NULL, m->path, m->interface, m->member, true)) - return 0; - } else { - Iterator i; - char *n; - - SET_FOREACH(n, owned_names, i) - if (policy_check_recv(policy, destination_uid, destination_gid, m->header->type, n, 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_hello(sd_bus *a, sd_bus *b, sd_bus_message *m, bool *got_hello) { - _cleanup_bus_message_unref_ sd_bus_message *n = NULL; - bool is_hello; - int r; - - assert(a); - assert(b); - assert(m); - assert(got_hello); - - /* 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 (*got_hello) - return 0; - - log_error("First packet isn't hello (it's %s.%s), aborting.", m->interface, m->member); - return -EIO; - } - - if (*got_hello) { - log_error("Got duplicate hello, aborting."); - return -EIO; - } - - *got_hello = true; - - if (!a->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", a->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(b, n); - if (r < 0) - return log_error_errno(r, "Failed to seal HELLO reply: %m"); - - r = sd_bus_send(b, 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( - b, - &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", a->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 = bus_seal_synthetic_message(b, n); - if (r < 0) - return log_error_errno(r, "Failed to seal NameAcquired message: %m"); - - r = sd_bus_send(b, 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 bus_new_destination(sd_bus **out, struct ucred *ucred, const char *peersec, bool negotiate_fds) { - _cleanup_bus_close_unref_ sd_bus *a = NULL; - int r; - - r = sd_bus_new(&a); - if (r < 0) - return log_error_errno(r, "Failed to allocate bus: %m"); - - r = sd_bus_set_description(a, "sd-proxy"); - if (r < 0) - return log_error_errno(r, "Failed to set bus name: %m"); - - r = sd_bus_set_address(a, arg_address); - if (r < 0) - return log_error_errno(r, "Failed to set address to connect to: %m"); - - r = sd_bus_negotiate_fds(a, negotiate_fds); - if (r < 0) - return log_error_errno(r, "Failed to set FD negotiation: %m"); - - r = sd_bus_negotiate_creds(a, true, SD_BUS_CREDS_UID|SD_BUS_CREDS_PID|SD_BUS_CREDS_GID|SD_BUS_CREDS_SELINUX_CONTEXT); - if (r < 0) - return log_error_errno(r, "Failed to set credential negotiation: %m"); - - if (ucred->pid > 0) { - a->fake_pids.pid = ucred->pid; - a->fake_pids_valid = true; - - a->fake_creds.uid = ucred->uid; - a->fake_creds.euid = UID_INVALID; - a->fake_creds.suid = UID_INVALID; - a->fake_creds.fsuid = UID_INVALID; - a->fake_creds.gid = ucred->gid; - a->fake_creds.egid = GID_INVALID; - a->fake_creds.sgid = GID_INVALID; - a->fake_creds.fsgid = GID_INVALID; - a->fake_creds_valid = true; - } - - if (peersec) { - a->fake_label = strdup(peersec); - if (!a->fake_label) - return log_oom(); - } - - a->manual_peer_interface = true; - - r = sd_bus_start(a); - if (r < 0) - return log_error_errno(r, "Failed to start bus client: %m"); - - *out = a; - a = NULL; - return 0; -} - -static int bus_new_local(sd_bus **out, sd_bus *dest, int in_fd, int out_fd, bool negotiate_fds) { - _cleanup_bus_close_unref_ sd_bus *b = NULL; - sd_id128_t server_id; - 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, in_fd, out_fd); - if (r < 0) - return log_error_errno(r, "Failed to set fds: %m"); - - r = sd_bus_get_bus_id(dest, &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_UID|SD_BUS_CREDS_PID|SD_BUS_CREDS_GID|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"); - - *out = b; - b = NULL; - return 0; -} - -static int mac_smack_apply_label_and_drop_cap_mac_admin(pid_t its_pid, const char *new_label) { -#ifdef HAVE_SMACK - int r = 0, k; - - if (!mac_smack_use()) - return 0; - - if (new_label && its_pid > 0) - r = mac_smack_apply_pid(its_pid, new_label); - - k = drop_capability(CAP_MAC_ADMIN); - return r < 0 ? r : k; -#else - return 0; -#endif -} - - int main(int argc, char *argv[]) { - - _cleanup_bus_close_unref_ sd_bus *a = NULL, *b = NULL; + _cleanup_(proxy_freep) Proxy *p = NULL; int r, in_fd, out_fd; - bool got_hello = false; - bool is_unix; - struct ucred ucred = {}; - _cleanup_free_ char *peersec = NULL; - Policy policy_buffer = {}, *policy = NULL; - _cleanup_set_free_free_ Set *owned_names = NULL; uid_t original_uid; log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); @@ -722,19 +275,6 @@ int main(int argc, char *argv[]) { original_uid = getuid(); - 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, &ucred); - (void) getpeersec(in_fd, &peersec); - - r = mac_smack_apply_label_and_drop_cap_mac_admin(getpid(), peersec); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK label (%s) and drop CAP_MAC_ADMIN: %m", peersec); - } - if (arg_drop_privileges) { const char *user = "systemd-bus-proxy"; uid_t uid; @@ -751,365 +291,29 @@ int main(int argc, char *argv[]) { goto finish; } - owned_names = set_new(&string_hash_ops); - if (!owned_names) { - log_oom(); + r = proxy_new(&p, in_fd, out_fd, arg_address); + if (r < 0) goto finish; - } - r = bus_new_destination(&a, &ucred, peersec, is_unix); + r = proxy_load_policy(p, arg_configuration); if (r < 0) goto finish; - if (a->is_kernel) { - if (!arg_configuration) { - const char *scope; - - r = sd_bus_get_scope(a, &scope); - if (r < 0) { - log_error_errno(r, "Couldn't determine bus scope: %m"); - goto finish; - } - - if (streq(scope, "system")) - arg_configuration = strv_new( - "/etc/dbus-1/system.conf", - "/etc/dbus-1/system.d/", - "/etc/dbus-1/system-local.conf", - NULL); - else if (streq(scope, "user")) - arg_configuration = strv_new( - "/etc/dbus-1/session.conf", - "/etc/dbus-1/session.d/", - "/etc/dbus-1/session-local.conf", - NULL); - else { - log_error("Unknown scope %s, don't know which policy to load. Refusing.", scope); - goto finish; - } - - if (!arg_configuration) { - r = log_oom(); - goto finish; - } - } - - r = policy_load(&policy_buffer, arg_configuration); - if (r < 0) { - log_error_errno(r, "Failed to load policy: %m"); - goto finish; - } - - policy = &policy_buffer; - /* policy_dump(policy); */ - - if (ucred.uid == original_uid) - log_debug("Permitting access, since bus owner matches bus client."); - else if (policy_check_hello(policy, ucred.uid, ucred.gid)) - log_debug("Permitting access due to XML policy."); - else { - r = log_error_errno(EPERM, "Policy denied connection."); - goto finish; - } - } - - r = bus_new_local(&b, a, in_fd, out_fd, is_unix); + r = proxy_hello_policy(p, original_uid); if (r < 0) goto finish; - r = rename_service(a, b); + r = rename_service(p->dest_bus, p->local_bus); if (r < 0) log_debug_errno(r, "Failed to rename process: %m"); - if (a->is_kernel) { - _cleanup_free_ char *match = NULL; - const char *unique; - - r = sd_bus_get_unique_name(a, &unique); - if (r < 0) { - log_error_errno(r, "Failed to get unique name: %m"); - goto finish; - } - - match = strjoin("type='signal'," - "sender='org.freedesktop.DBus'," - "path='/org/freedesktop/DBus'," - "interface='org.freedesktop.DBus'," - "member='NameOwnerChanged'," - "arg1='", - unique, - "'", - NULL); - if (!match) { - log_oom(); - goto finish; - } - - r = sd_bus_add_match(a, NULL, match, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add match for NameLost: %m"); - goto finish; - } - - 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) { - log_oom(); - goto finish; - } - - r = sd_bus_add_match(a, NULL, match, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add match for NameAcquired: %m"); - goto finish; - } - } - - for (;;) { - _cleanup_bus_message_unref_ sd_bus_message *m = NULL; - int events_a, events_b, fd; - uint64_t timeout_a, timeout_b, t; - struct timespec _ts, *ts; - struct pollfd *pollfd; - int k; - - if (got_hello) { - /* Read messages from bus, to pass them on to our client */ - - r = sd_bus_process(a, &m); - if (r < 0) { - /* treat 'connection reset by peer' as clean exit condition */ - if (r == -ECONNRESET) - r = 0; - else - log_error_errno(r, "Failed to process bus a: %m"); - - goto finish; - } - - if (m) { - bool processed = false; - - /* We officially got EOF, let's quit */ - if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) { - r = 0; - goto finish; - } - - k = synthesize_name_acquired(a, b, m); - if (k < 0) { - r = k; - log_error_errno(r, "Failed to synthesize message: %m"); - goto finish; - } - - patch_sender(a, m); - - if (policy) { - k = process_policy(a, b, m, policy, &ucred, owned_names); - if (k < 0) { - r = k; - log_error_errno(r, "Failed to process policy: %m"); - goto finish; - } else if (k > 0) { - r = 1; - processed = true; - } - } - - if (!processed) { - k = sd_bus_send(b, m, NULL); - if (k < 0) { - if (k == -ECONNRESET) { - r = 0; - goto finish; - } else if (k == -EPERM && m->reply_cookie > 0) { - /* If the peer tries to send a reply and it is rejected with EPERM - * 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 - * EPERM 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. */ - r = 1; - } else { - r = k; - log_error_errno(r, "Failed to send message to client: %m"); - goto finish; - } - } else - r = 1; - } - } - - if (r > 0) - continue; - } - - /* Read messages from our client, to pass them on to the bus */ - r = sd_bus_process(b, &m); - if (r < 0) { - /* treat 'connection reset by peer' as clean exit condition */ - if (r == -ECONNRESET) - r = 0; - else - log_error_errno(r, "Failed to process bus b: %m"); - - goto finish; - } - - if (m) { - bool processed = false; - - /* We officially got EOF, let's quit */ - if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected")) { - r = 0; - goto finish; - } - - k = process_hello(a, b, m, &got_hello); - if (k < 0) { - r = k; - log_error_errno(r, "Failed to process HELLO: %m"); - goto finish; - } else if (k > 0) { - processed = true; - r = 1; - } - - if (!processed) { - k = bus_proxy_process_driver(a, b, m, policy, &ucred, owned_names); - if (k < 0) { - r = k; - log_error_errno(r, "Failed to process driver calls: %m"); - goto finish; - } else if (k > 0) { - processed = true; - r = 1; - } - - if (!processed) { - - for (;;) { - if (policy) { - k = process_policy(b, a, m, policy, &ucred, owned_names); - if (k < 0) { - r = k; - log_error_errno(r, "Failed to process policy: %m"); - goto finish; - } else if (k > 0) { - processed = true; - r = 1; - break; - } - } - - k = sd_bus_send(a, m, NULL); - if (k < 0) { - if (k == -EREMCHG) { - /* The name database changed since the policy check, hence let's check again */ - continue; - } else if (k == -ECONNRESET) { - r = 0; - goto finish; - } else if (k == -EPERM && m->reply_cookie > 0) { - /* see above why EPERM is ignored for replies */ - r = 1; - } else { - r = k; - log_error_errno(r, "Failed to send message to bus: %m"); - goto finish; - } - } else - r = 1; - - break; - } - } - } - } - - if (r > 0) - continue; - - fd = sd_bus_get_fd(a); - if (fd < 0) { - log_error_errno(r, "Failed to get fd: %m"); - goto finish; - } - - events_a = sd_bus_get_events(a); - if (events_a < 0) { - log_error_errno(r, "Failed to get events mask: %m"); - goto finish; - } - - r = sd_bus_get_timeout(a, &timeout_a); - if (r < 0) { - log_error_errno(r, "Failed to get timeout: %m"); - goto finish; - } - - events_b = sd_bus_get_events(b); - if (events_b < 0) { - log_error_errno(r, "Failed to get events mask: %m"); - goto finish; - } - - r = sd_bus_get_timeout(b, &timeout_b); - if (r < 0) { - log_error_errno(r, "Failed to get timeout: %m"); - goto finish; - } - - t = timeout_a; - if (t == (uint64_t) -1 || (timeout_b != (uint64_t) -1 && timeout_b < timeout_a)) - t = timeout_b; - - 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_a, }, - {.fd = in_fd, .events = events_b & POLLIN, }, - {.fd = out_fd, .events = events_b & POLLOUT, } - }; - - r = ppoll(pollfd, 3, ts, NULL); - if (r < 0) { - log_error_errno(errno, "ppoll() failed: %m"); - goto finish; - } - } + r = proxy_run(p); finish: sd_notify(false, "STOPPING=1\n" "STATUS=Shutting down."); - policy_free(&policy_buffer); strv_free(arg_configuration); free(arg_address); diff --git a/src/bus-proxyd/proxy.c b/src/bus-proxyd/proxy.c new file mode 100644 index 0000000000..744b11da5c --- /dev/null +++ b/src/bus-proxyd/proxy.c @@ -0,0 +1,859 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + 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 <sys/socket.h> +#include <sys/un.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/poll.h> +#include <stddef.h> +#include <getopt.h> + +#include "log.h" +#include "util.h" +#include "socket-util.h" +#include "sd-daemon.h" +#include "sd-bus.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "build.h" +#include "strv.h" +#include "def.h" +#include "capability.h" +#include "bus-control.h" +#include "smack-util.h" +#include "set.h" +#include "bus-xml-policy.h" +#include "driver.h" +#include "proxy.h" +#include "synthesize.h" + +static int proxy_create_dest(Proxy *p, const char *dest, const char *local_sec, bool negotiate_fds) { + _cleanup_bus_close_unref_ 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, dest); + 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_UID|SD_BUS_CREDS_PID|SD_BUS_CREDS_GID|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 = p->local_creds.uid; + b->fake_creds.euid = UID_INVALID; + b->fake_creds.suid = UID_INVALID; + b->fake_creds.fsuid = UID_INVALID; + b->fake_creds.gid = p->local_creds.gid; + b->fake_creds.egid = GID_INVALID; + 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->dest_bus = b; + b = NULL; + return 0; +} + +static int proxy_create_local(Proxy *p, int in_fd, int out_fd, bool negotiate_fds) { + _cleanup_bus_close_unref_ sd_bus *b = NULL; + sd_id128_t server_id; + 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, in_fd, out_fd); + if (r < 0) + return log_error_errno(r, "Failed to set fds: %m"); + + r = sd_bus_get_bus_id(p->dest_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_UID|SD_BUS_CREDS_PID|SD_BUS_CREDS_GID|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"); + + p->local_bus = b; + b = NULL; + return 0; +} + +static int proxy_prepare_matches(Proxy *p) { + _cleanup_free_ char *match = NULL; + const char *unique; + int r; + + if (!p->dest_bus->is_kernel) + return 0; + + r = sd_bus_get_unique_name(p->dest_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->dest_bus, NULL, match, NULL, NULL); + 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->dest_bus, NULL, match, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add match for NameAcquired: %m"); + + return 0; +} + +int proxy_new(Proxy **out, int in_fd, int out_fd, const char *dest) { + _cleanup_(proxy_freep) Proxy *p = NULL; + _cleanup_free_ char *local_sec = NULL; + bool is_unix; + int r; + + p = new0(Proxy, 1); + if (!p) + 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_dest(p, dest, local_sec, is_unix); + if (r < 0) + return r; + + r = proxy_create_local(p, in_fd, out_fd, 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) { + if (!p) + return NULL; + + sd_bus_close_unrefp(&p->local_bus); + sd_bus_close_unrefp(&p->dest_bus); + set_free_free(p->owned_names); + free(p); + + return NULL; +} + +int proxy_load_policy(Proxy *p, char **configuration) { + _cleanup_strv_free_ char **strv = NULL; + int r; + + assert(p); + + /* no need to load legacy policy if destination is not kdbus */ + if (!p->dest_bus->is_kernel) + return 0; + + if (!configuration) { + const char *scope; + + r = sd_bus_get_scope(p->dest_bus, &scope); + if (r < 0) + return log_error_errno(r, "Couldn't determine bus scope: %m"); + + if (streq(scope, "system")) + strv = strv_new("/etc/dbus-1/system.conf", + "/etc/dbus-1/system.d/", + "/etc/dbus-1/system-local.conf", + NULL); + else if (streq(scope, "user")) + strv = strv_new("/etc/dbus-1/session.conf", + "/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; + } + + r = policy_load(&p->policy_buffer, configuration); + if (r < 0) + return log_error_errno(r, "Failed to load policy: %m"); + + p->policy = &p->policy_buffer; + /* policy_dump(p->policy); */ + + return 0; +} + +int proxy_hello_policy(Proxy *p, uid_t original_uid) { + assert(p); + + if (!p->policy) + return 0; + + if (p->local_creds.uid == original_uid) + log_debug("Permitting access, since bus owner matches bus client."); + else if (policy_check_hello(p->policy, p->local_creds.uid, p->local_creds.gid)) + log_debug("Permitting access due to XML policy."); + else + return log_error_errno(EPERM, "Policy denied connection."); + + return 0; +} + +static int proxy_wait(Proxy *p) { + uint64_t timeout_dest, timeout_local, t; + int events_dest, events_local, fd; + struct timespec _ts, *ts; + struct pollfd *pollfd; + int r; + + assert(p); + + fd = sd_bus_get_fd(p->dest_bus); + if (fd < 0) + return log_error_errno(fd, "Failed to get fd: %m"); + + events_dest = sd_bus_get_events(p->dest_bus); + if (events_dest < 0) + return log_error_errno(events_dest, "Failed to get events mask: %m"); + + r = sd_bus_get_timeout(p->dest_bus, &timeout_dest); + 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_dest; + if (t == (uint64_t) -1 || (timeout_local != (uint64_t) -1 && timeout_local < timeout_dest)) + 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_dest, }, + { .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(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; + bool granted = false; + + /* 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. */ + sd_bus_creds_get_well_known_names(&m->creds, &sender_names); + + (void) sd_bus_creds_get_uid(&m->creds, &sender_uid); + (void) sd_bus_creds_get_gid(&m->creds, &sender_gid); + + if (sender_uid == UID_INVALID || sender_gid == GID_INVALID) { + _cleanup_bus_creds_unref_ 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_UID|SD_BUS_CREDS_GID, true, &sender_creds); + if (r < 0) + return handle_policy_error(m, r); + + (void) sd_bus_creds_get_uid(sender_creds, &sender_uid); + (void) sd_bus_creds_get_gid(sender_creds, &sender_gid); + } + + /* First check whether the sender can send the message to our name */ + if (set_isempty(owned_names)) { + if (policy_check_send(policy, sender_uid, sender_gid, m->header->type, NULL, m->path, m->interface, m->member, false)) + granted = true; + } else { + Iterator i; + char *n; + + SET_FOREACH(n, owned_names, i) + if (policy_check_send(policy, sender_uid, sender_gid, m->header->type, n, m->path, m->interface, m->member, false)) { + granted = true; + break; + } + } + + if (granted) { + /* Then check whether us (the recipient) can receive from the sender's name */ + if (strv_isempty(sender_names)) { + if (policy_check_recv(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, m->path, m->interface, m->member, false)) + return 0; + } else { + char **n; + + STRV_FOREACH(n, sender_names) { + if (policy_check_recv(policy, our_ucred->uid, our_ucred->gid, m->header->type, *n, 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_bus_creds_unref_ 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; + bool granted = false; + + /* 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_UID|SD_BUS_CREDS_GID|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); + + sd_bus_creds_get_well_known_names(destination_creds, &destination_names); + + (void) sd_bus_creds_get_uid(destination_creds, &destination_uid); + (void) sd_bus_creds_get_gid(destination_creds, &destination_gid); + } + + /* First check if we (the sender) can send to this name */ + if (strv_isempty(destination_names)) { + if (policy_check_send(policy, our_ucred->uid, our_ucred->gid, m->header->type, NULL, m->path, m->interface, m->member, true)) + granted = true; + } else { + char **n; + + STRV_FOREACH(n, destination_names) { + if (policy_check_send(policy, our_ucred->uid, our_ucred->gid, m->header->type, *n, m->path, m->interface, m->member, true)) { + + /* 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) + break; + + granted = true; + break; + } + } + } + + /* Then check if the recipient can receive from our name */ + if (granted) { + 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 + * receiver policies that match on receiver + * credentials. We know sd-bus always sets + * KDBUS_MSG_SIGNAL, so the kernel applies + * receiver policies to the message. Therefore, + * skip policy checks in this case. */ + return 0; + } else if (set_isempty(owned_names)) { + if (policy_check_recv(policy, destination_uid, destination_gid, m->header->type, NULL, m->path, m->interface, m->member, true)) + return 0; + } else { + Iterator i; + char *n; + + SET_FOREACH(n, owned_names, i) + if (policy_check_recv(policy, destination_uid, destination_gid, m->header->type, n, 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_hello(Proxy *p, sd_bus_message *m) { + _cleanup_bus_message_unref_ 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->dest_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->dest_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->dest_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 = 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_dest_to_local(Proxy *p) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + assert(p); + + r = sd_bus_process(p->dest_bus, &m); + if (r < 0) { + /* treat 'connection reset by peer' as clean exit condition */ + if (r != -ECONNRESET) + 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->dest_bus, p->local_bus, m); + if (r < 0) + return log_error_errno(r, "Failed to synthesize message: %m"); + + patch_sender(p->dest_bus, m); + + if (p->policy) { + r = process_policy(p->dest_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); + if (r < 0) + return log_error_errno(r, "Failed to process policy: %m"); + else if (r > 0) + return 1; + } + + r = sd_bus_send(p->local_bus, m, NULL); + if (r < 0) { + if (r == -EPERM && m->reply_cookie > 0) { + /* If the peer tries to send a reply and it is rejected with EPERM + * 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 + * EPERM 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. */ + return 1; + } else { + if (r != -ECONNRESET) + log_error_errno(r, "Failed to send message to client: %m"); + return r; + } + } + + return 1; +} + +static int proxy_process_local_to_dest(Proxy *p) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + int r; + + assert(p); + + r = sd_bus_process(p->local_bus, &m); + if (r < 0) { + /* treat 'connection reset by peer' as clean exit condition */ + if (r != -ECONNRESET) + 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 < 0) + return log_error_errno(r, "Failed to process HELLO: %m"); + else if (r > 0) + return 1; + + r = bus_proxy_process_driver(p->dest_bus, p->local_bus, m, p->policy, &p->local_creds, p->owned_names); + if (r < 0) + return log_error_errno(r, "Failed to process driver calls: %m"); + else if (r > 0) + return 1; + + for (;;) { + if (p->policy) { + r = process_policy(p->local_bus, p->dest_bus, m, p->policy, &p->local_creds, p->owned_names); + if (r < 0) + return log_error_errno(r, "Failed to process policy: %m"); + else if (r > 0) + return 1; + } + + r = sd_bus_send(p->dest_bus, m, NULL); + if (r < 0) { + if (r == -EREMCHG) { + /* The name database changed since the policy check, hence let's check again */ + continue; + } else if (r == -EPERM && m->reply_cookie > 0) { + /* see above why EPERM is ignored for replies */ + return 1; + } else { + if (r != -ECONNRESET) + log_error_errno(r, "Failed to send message to bus: %m"); + return r; + } + } + + break; + } + + return 1; +} + +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_dest_to_local(p); + if (r == -ECONNRESET) + return 0; + else if (r < 0) + return r; + else if (r > 0) + busy = true; + } + + /* Read messages from our client, to pass them on to the bus */ + r = proxy_process_local_to_dest(p); + if (r == -ECONNRESET) + return 0; + else if (r < 0) + return r; + else if (r > 0) + busy = true; + + if (!busy) { + r = proxy_wait(p); + 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..08994fe502 --- /dev/null +++ b/src/bus-proxyd/proxy.h @@ -0,0 +1,53 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#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 <stdlib.h> +#include "sd-bus.h" +#include "bus-xml-policy.h" +#include "util.h" + +typedef struct Proxy Proxy; + +struct Proxy { + sd_bus *local_bus; + struct ucred local_creds; + int local_in; + int local_out; + + sd_bus *dest_bus; + + Set *owned_names; + Policy policy_buffer; + Policy *policy; + + bool got_hello : 1; +}; + +int proxy_new(Proxy **out, int in_fd, int out_fd, const char *dest); +Proxy *proxy_free(Proxy *p); + +int proxy_load_policy(Proxy *p, char **configuration); +int proxy_hello_policy(Proxy *p, uid_t original_uid); +int proxy_run(Proxy *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Proxy*, proxy_free); |