/*
 * D-Bus Broker Launcher
 */

#include <c-macro.h>
#include <c-rbtree.h>
#include <c-string.h>
#include <fcntl.h>
#include <getopt.h>
#include <glib.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-daemon.h>
#include <systemd/sd-event.h>
#include <systemd/sd-id128.h>
#include <unistd.h>
#include "launch/config.h"
#include "launch/nss-cache.h"
#include "launch/policy.h"
#include "util/apparmor.h"
#include "util/audit.h"
#include "util/dirwatch.h"
#include "util/error.h"
#include "util/log.h"
#include "util/misc.h"

typedef struct Manager Manager;
typedef struct Service Service;

enum {
        _MAIN_SUCCESS,
        MAIN_EXIT,
        MAIN_FAILED,
};

enum {
        _MANAGER_E_SUCCESS,

        MANAGER_E_INVALID_CONFIG,
};

typedef enum {
        SERVICE_STATE_PENDING,
        SERVICE_STATE_CURRENT,
        SERVICE_STATE_DEFUNCT,
} ServiceState;

struct Service {
        Manager *manager;
        ServiceState state;
        sd_bus_slot *slot;
        CRBNode rb;
        CRBNode rb_by_name;
        char *name;
        char *unit;
        size_t argc;
        char **argv;
        char *user;
        uid_t uid;
        uint64_t instance;
        char id[];
};

struct Manager {
        sd_event *event;
        sd_bus *bus_controller;
        sd_bus *bus_regular;
        int fd_listen;
        Dirwatch *dirwatch;
        sd_event_source *dirwatch_src;
        CRBTree services;
        CRBTree services_by_name;
        uint64_t service_ids;
        uint32_t uid;
        uint32_t gid;
};

static bool             main_arg_audit = false;
static const char *     main_arg_broker = BINDIR "/dbus-broker";
static const char *     main_arg_configfile = NULL;
static bool             main_arg_user_scope = false;
static Log              main_log = LOG_NULL;

static sd_bus *bus_close_unref(sd_bus *bus) {
        /*
         * It is not sufficient to simply call sd_bus_unref(), as messages
         * in the bus' queues may pin the bus itself. Also,
         * sd_bus_flush_close_unref() is not always appropriate as it would
         * block in poll waiting for messages to be flushed to the socket.
         *
         * In some cases all we really want to do is close the socket and
         * release all the memory, ignoring whether or not it has been
         * flushed to the kernel (typically in error paths).
         */
        if (!bus)
                return NULL;

        sd_bus_close(bus);

        return sd_bus_unref(bus);
}

static int service_compare(CRBTree *t, void *k, CRBNode *n) {
        Service *service = c_container_of(n, Service, rb);

        return strcmp(k, service->id);
}

static int service_compare_by_name(CRBTree *t, void *k, CRBNode *n) {
        Service *service = c_container_of(n, Service, rb_by_name);

        return strcmp(k, service->name);
}

static Service *service_free(Service *service) {
        if (!service)
                return NULL;

        c_rbnode_unlink(&service->rb_by_name);
        c_rbnode_unlink(&service->rb);
        free(service->user);
        for (size_t i = 0; i < service->argc; ++i)
                free(service->argv[i]);
        free(service->argv);
        free(service->unit);
        free(service->name);
        sd_bus_slot_unref(service->slot);
        free(service);

        return NULL;
}

C_DEFINE_CLEANUP(Service *, service_free);

static int service_update(Service *service, const char *unit, size_t argc, char **argv, const char *user, uid_t uid) {
        service->unit = c_free(service->unit);
        service->argc = 0;
        service->argv = c_free(service->argv);
        service->user = c_free(service->user);
        service->uid = uid;

        if (unit) {
                service->unit = strdup(unit);
                if (!service->unit)
                        return error_origin(-ENOMEM);
        }

        if (argc > 0) {
                service->argv = calloc(1, argc * sizeof(char*));
                if (!service->argv)
                        return error_origin(-ENOMEM);

                service->argc = argc;

                for (size_t i = 0; i < argc; ++i) {
                        service->argv[i] = strdup(argv[i]);
                        if (!service->argv[i])
                                return error_origin(-ENOMEM);
                }
        }

        if (user) {
                service->user = strdup(user);
                if (!service->user)
                        return error_origin(-ENOMEM);
        }

        return 0;
}

static int service_new(Service **servicep,
                       Manager *manager,
                       const char *name,
                       CRBNode **slot_by_name,
                       CRBNode *parent_by_name,
                       const char *unit,
                       size_t argc,
                       char **argv,
                       const char *user,
                       uid_t uid) {
        _c_cleanup_(service_freep) Service *service = NULL;
        CRBNode **slot, *parent;
        int r;

        service = calloc(1, sizeof(*service) + C_DECIMAL_MAX(uint64_t) + 1);
        if (!service)
                return error_origin(-ENOMEM);

        service->manager = manager;
        service->rb = (CRBNode)C_RBNODE_INIT(service->rb);
        service->rb_by_name = (CRBNode)C_RBNODE_INIT(service->rb_by_name);
        sprintf(service->id, "%" PRIu64, ++manager->service_ids);

        service->name = strdup(name);
        if (!service->name)
                return error_origin(-ENOMEM);

        r = service_update(service, unit, argc, argv, user, uid);
        if (r)
                return error_trace(r);

        slot = c_rbtree_find_slot(&manager->services, service_compare, service->id, &parent);
        assert(slot);
        c_rbtree_add(&manager->services, parent, slot, &service->rb);
        c_rbtree_add(&manager->services_by_name, parent_by_name, slot_by_name, &service->rb_by_name);

        *servicep = service;
        service = NULL;
        return 0;
}

static Manager *manager_free(Manager *manager) {
        Service *service, *safe;

        if (!manager)
                return NULL;

        c_rbtree_for_each_entry_safe_postorder_unlink(service, safe, &manager->services, rb)
                service_free(service);
        assert(c_rbtree_is_empty(&manager->services_by_name));

        sd_event_source_unref(manager->dirwatch_src);
        dirwatch_free(manager->dirwatch);
        c_close(manager->fd_listen);
        bus_close_unref(manager->bus_regular);
        bus_close_unref(manager->bus_controller);
        sd_event_unref(manager->event);
        free(manager);

        return NULL;
}

C_DEFINE_CLEANUP(Manager *, manager_free);

static int manager_reload_config(Manager *manager);

static int manager_on_sighup(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
        Manager *manager = userdata;
        int r;

        fprintf(stderr, "Caught SIGHUP\n");

        r = manager_reload_config(manager);
        if (r) {
                if (r == MANAGER_E_INVALID_CONFIG)
                        fprintf(stderr, "Invalid configuration, ignored.\n");
                else
                        return error_fold(r);
        }

        return 1;
}

static int manager_on_dirwatch(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
        Manager *manager = userdata;
        int r;

        r = dirwatch_dispatch(manager->dirwatch);
        if (r != DIRWATCH_E_TRIGGERED)
                return error_fold(r);

        fprintf(stderr, "Noticed file-system modification, trigger reload\n");

        r = manager_reload_config(manager);
        if (r) {
                if (r == MANAGER_E_INVALID_CONFIG)
                        fprintf(stderr, "Invalid configuration, ignored.\n");
                else
                        return error_fold(r);
        }

        return 1;
}

static int manager_new(Manager **managerp) {
        _c_cleanup_(manager_freep) Manager *manager = NULL;
        int r;

        manager = calloc(1, sizeof(*manager));
        if (!manager)
                return error_origin(-ENOMEM);

        manager->fd_listen = -1;
        manager->uid = -1;
        manager->gid = -1;

        r = sd_event_default(&manager->event);
        if (r < 0)
                return error_origin(r);

        r = sd_event_add_signal(manager->event, NULL, SIGTERM, NULL, NULL);
        if (r < 0)
                return error_origin(r);

        r = sd_event_add_signal(manager->event, NULL, SIGINT, NULL, NULL);
        if (r < 0)
                return error_origin(r);

        r = sd_event_add_signal(manager->event, NULL, SIGHUP, manager_on_sighup, manager);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_new(&manager->bus_controller);
        if (r < 0)
                return error_origin(r);

        *managerp = manager;
        manager = NULL;
        return 0;
}

static int manager_listen_inherit(Manager *manager) {
        _c_cleanup_(c_closep) int s = -1;
        int r, n;

        assert(manager->fd_listen < 0);

        n = sd_listen_fds(true);
        if (n < 0)
                return error_origin(n);

        if (n == 0) {
                fprintf(stderr, "No listener socket inherited\n");
                return MAIN_FAILED;
        }
        if (n > 1) {
                fprintf(stderr, "More than one listener socket passed\n");
                return MAIN_FAILED;
        }

        s = SD_LISTEN_FDS_START;

        r = sd_is_socket(s, PF_UNIX, SOCK_STREAM, 1);
        if (r < 0)
                return error_origin(r);

        if (!r) {
                fprintf(stderr, "Non unix-domain-socket passed as listener\n");
                return MAIN_FAILED;
        }

        r = fcntl(s, F_GETFL);
        if (r < 0)
                return error_origin(-errno);

        r = fcntl(s, F_SETFL, r | O_NONBLOCK);
        if (r < 0)
                return error_origin(-errno);

        manager->fd_listen = s;
        s = -1;
        return 0;
}

static noreturn void manager_run_child(Manager *manager, int fd_log, int fd_controller) {
        sd_id128_t machine_id;
        char str_log[C_DECIMAL_MAX(int) + 1], str_controller[C_DECIMAL_MAX(int) + 1], str_machine_id[33];
        const char * const argv[] = {
                "dbus-broker",
                "--log",
                str_log,
                "--controller",
                str_controller,
                "--machine-id",
                str_machine_id,
                main_arg_audit ? "--audit" : NULL, /* note that this needs to be the last argument to work */
                NULL,
        };
        int r;

        if (manager->uid != (uint32_t)-1) {
                r = util_audit_drop_permissions(manager->uid, manager->gid);
                if (r)
                        goto exit;
        }

        r = prctl(PR_SET_PDEATHSIG, SIGTERM);
        if (r) {
                r = error_origin(-errno);
                goto exit;
        }

        r = fcntl(fd_log, F_GETFD);
        if (r < 0) {
                r = error_origin(-errno);
                goto exit;
        }

        r = fcntl(fd_log, F_SETFD, r & ~FD_CLOEXEC);
        if (r < 0) {
                r = error_origin(-errno);
                goto exit;
        }

        r = fcntl(fd_controller, F_GETFD);
        if (r < 0) {
                r = error_origin(-errno);
                goto exit;
        }

        r = fcntl(fd_controller, F_SETFD, r & ~FD_CLOEXEC);
        if (r < 0) {
                r = error_origin(-errno);
                goto exit;
        }

        r = sd_id128_get_machine(&machine_id);
        if (r < 0) {
                r = error_origin(r);
                goto exit;
        }

        sd_id128_to_string(machine_id, str_machine_id);

        r = snprintf(str_log, sizeof(str_log), "%d", fd_log);
        assert(r < (ssize_t)sizeof(str_log));

        r = snprintf(str_controller, sizeof(str_controller), "%d", fd_controller);
        assert(r < (ssize_t)sizeof(str_controller));

        r = execve(main_arg_broker, (char * const *)argv, environ);
        r = error_origin(-errno);

exit:
        _exit(1);
}

static int manager_on_child_exit(sd_event_source *source, const siginfo_t *si, void *userdata) {
        fprintf(stderr, "Caught SIGCHLD of broker\n");

        return sd_event_exit(sd_event_source_get_event(source),
                             (si->si_code == CLD_EXITED) ? si->si_status : EXIT_FAILURE);
}

static int manager_fork(Manager *manager, int fd_controller) {
        pid_t pid;
        int r;

        pid = fork();
        if (pid < 0)
                return error_origin(-errno);

        if (!pid)
                manager_run_child(manager, log_get_fd(&main_log), fd_controller);

        r = sd_event_add_child(manager->event, NULL, pid, WEXITED, manager_on_child_exit, manager);
        if (r < 0)
                return error_origin(-errno);

        close(fd_controller);
        return 0;
}

static int manager_start_unit_handler(sd_bus_message *message, void *userdata, sd_bus_error *errorp) {
        _c_cleanup_(c_freep) char *object_path = NULL;
        const sd_bus_error *error;
        Service *service = userdata;
        int r;

        service->slot = sd_bus_slot_unref(service->slot);

        error = sd_bus_message_get_error(message);
        if (!error)
                /* unit started successfully */
                return 1;

        fprintf(stderr, "Activation request for '%s' failed: %s\n", service->name, error->message);

        /* unit failed, so reset pending activation requsets in the broker */
        r = asprintf(&object_path, "/org/bus1/DBus/Name/%s", service->id);
        if (r < 0)
                return error_origin(-errno);

        r = sd_bus_call_method(service->manager->bus_controller,
                               NULL,
                               object_path,
                               "org.bus1.DBus.Name",
                               "Reset",
                               NULL,
                               NULL,
                               "");
        if (r < 0)
                return error_origin(r);

        return 1;
}

static int manager_start_unit(Manager *manager, Service *service) {
        _c_cleanup_(sd_bus_message_unrefp) sd_bus_message *method_call = NULL;
        int r;

        service->slot = sd_bus_slot_unref(service->slot);

        r = sd_bus_message_new_method_call(manager->bus_regular, &method_call,
                                           "org.freedesktop.systemd1",
                                           "/org/freedesktop/systemd1",
                                           "org.freedesktop.systemd1.Manager",
                                           "StartUnit");
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_append(method_call, "ss", service->unit, "replace");
        if (r < 0)
                return error_origin(r);

        r = sd_bus_call_async(manager->bus_regular, &service->slot, method_call, manager_start_unit_handler, service, -1);
        if (r < 0)
                return error_origin(r);

        return 0;
}

static int manager_start_transient_unit(Manager *manager, Service *service) {
        _c_cleanup_(sd_bus_message_unrefp) sd_bus_message *method_call = NULL;
        _c_cleanup_(c_freep) char *unit = NULL;
        const char *unique_name;
        int r;

        service->slot = sd_bus_slot_unref(service->slot);

        r = sd_bus_get_unique_name(manager->bus_regular, &unique_name);
        if (r < 0)
                return error_origin(r);

        r = asprintf(&unit, "dbus-%s-%s@%"PRIu64".service", unique_name, service->name, service->instance++);
        if (r < 0)
                return error_origin(-errno);

        r = sd_bus_message_new_method_call(manager->bus_regular, &method_call,
                                           "org.freedesktop.systemd1",
                                           "/org/freedesktop/systemd1",
                                           "org.freedesktop.systemd1.Manager",
                                           "StartTransientUnit");
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_append(method_call, "ss", unit, "replace");
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_open_container(method_call, 'a', "(sv)");
        if (r < 0)
                return error_origin(r);

        {
                r = sd_bus_message_open_container(method_call, 'r', "sv");
                if (r < 0)
                        return error_origin(r);

                {
                        r = sd_bus_message_append(method_call, "s", "ExecStart");
                        if (r < 0)
                                return error_origin(r);

                        r = sd_bus_message_open_container(method_call, 'v', "a(sasb)");
                        if (r < 0)
                                return error_origin(r);

                        {
                                r = sd_bus_message_open_container(method_call, 'a', "(sasb)");
                                if (r < 0)
                                        return error_origin(r);

                                {
                                        r = sd_bus_message_open_container(method_call, 'r', "sasb");
                                        if (r < 0)
                                                return error_origin(r);

                                        {
                                                r = sd_bus_message_append(method_call, "s", service->argv[0]);
                                                if (r < 0)
                                                        return error_origin(r);

                                                r = sd_bus_message_open_container(method_call, 'a', "s");
                                                if (r < 0)
                                                        return error_origin(r);

                                                {
                                                        for (size_t i = 0; i < service->argc; ++i) {
                                                                r = sd_bus_message_append(method_call, "s", service->argv[i]);
                                                                if (r < 0)
                                                                        return error_origin(r);
                                                        }
                                                }

                                                r = sd_bus_message_close_container(method_call);
                                                if (r < 0)
                                                        return error_origin(r);

                                                r = sd_bus_message_append(method_call, "b", true);
                                                if (r < 0)
                                                        return error_origin(r);
                                        }

                                        r = sd_bus_message_close_container(method_call);
                                        if (r < 0)
                                                return error_origin(r);
                                }

                                r = sd_bus_message_close_container(method_call);
                                if (r < 0)
                                        return error_origin(r);
                        }

                        r = sd_bus_message_close_container(method_call);
                        if (r < 0)
                                return error_origin(r);
                }

                r = sd_bus_message_close_container(method_call);
                if (r < 0)
                        return error_origin(r);

                r = sd_bus_message_open_container(method_call, 'r', "sv");
                if (r < 0)
                        return error_origin(r);

                {
                        r = sd_bus_message_append(method_call, "s", "KillMode");
                        if (r < 0)
                                return error_origin(r);

                        r = sd_bus_message_open_container(method_call, 'v', "s");
                        if (r < 0)
                                return error_origin(r);

                        {
                                r = sd_bus_message_append(method_call, "s", "process");
                                if (r < 0)
                                        return error_origin(r);
                        }

                        r = sd_bus_message_close_container(method_call);
                        if (r < 0)
                                return error_origin(r);
                }

                r = sd_bus_message_close_container(method_call);
                if (r < 0)
                        return error_origin(r);

                if (service->user) {
                        /*
                         * Ideally we would unconditionally pass the UID
                         * we are accounting on to systemd to run the service
                         * under. However, in the case of the user instance,
                         * systemd fails to start a transient unit if a user
                         * is provided due to lack of permission. In practice
                         * this works out ok, as in this case we would have
                         * provided our own UID, which is systemd's UID, so
                         * it would ammount to a no-op. It would have been
                         * better if systemd could detect this case and not
                         * fail, but in practice this is perfectly fine.
                         */
                        r = sd_bus_message_open_container(method_call, 'r', "sv");
                        if (r < 0)
                                return error_origin(r);

                        {
                                r = sd_bus_message_append(method_call, "s", "User");
                                if (r < 0)
                                        return error_origin(r);

                                r = sd_bus_message_open_container(method_call, 'v', "s");
                                if (r < 0)
                                        return error_origin(r);

                                {
                                        _c_cleanup_(c_freep) char *uid = NULL;

                                        /*
                                         * Pass the UID we parsed, rather than the
                                         * original username. This should resolve
                                         * to the same, but out of an abundance of
                                         * caution, we try to avoid any
                                         * inconsistencies.
                                         */
                                        r = asprintf(&uid, "%"PRIu32, service->uid);
                                        if (r < 0)
                                                return error_origin(-errno);

                                        r = sd_bus_message_append(method_call, "s", uid);
                                        if (r < 0)
                                                return error_origin(r);
                                }

                                r = sd_bus_message_close_container(method_call);
                                if (r < 0)
                                        return error_origin(r);
                        }

                        r = sd_bus_message_close_container(method_call);
                        if (r < 0)
                                return error_origin(r);
                }
        }

        r = sd_bus_message_close_container(method_call);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_append(method_call, "a(sa(sv))", 0);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_call_async(manager->bus_regular, &service->slot, method_call, manager_start_unit_handler, service, -1);
        if (r < 0)
                return error_origin(r);

        return 0;
}

static int manager_on_name_activate(Manager *manager, sd_bus_message *m, const char *id) {
        Service *service;
        int r;

        service = c_rbtree_find_entry(&manager->services,
                                      service_compare,
                                      id,
                                      Service,
                                      rb);
        if (!service) {
                fprintf(stderr, "Activation request on unknown name '%s'\n", id);
                return 0;
        } else if (!strcmp(service->name, "org.freedesktop.systemd1")) {
                /* pid1 activation requests are silently ignored */
                return 0;
        }

        if (service->unit) {
                r = manager_start_unit(manager, service);
                if (r)
                        return error_trace(r);
        } else {
                r = manager_start_transient_unit(manager, service);
                if (r)
                        return error_trace(r);
        }

        return 0;
}

static int manager_set_environment_handler(sd_bus_message *message, void *userdata, sd_bus_error *errorp) {
        const sd_bus_error *error;

        error = sd_bus_message_get_error(message);
        if (!error)
                /* environment set successfully */
                return 1;

        fprintf(stderr, "Updating activation environment failed: %s\n", error->message);

        return 1;
}

static int manager_on_set_activation_environment(Manager *manager, sd_bus_message *m) {
        _c_cleanup_(sd_bus_message_unrefp) sd_bus_message *method_call = NULL;
        int r;

        r = sd_bus_message_new_method_call(manager->bus_regular, &method_call,
                                           "org.freedesktop.systemd1",
                                           "/org/freedesktop/systemd1",
                                           "org.freedesktop.systemd1.Manager",
                                           "SetEnvironment");
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_enter_container(m, 'a', "{ss}");
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_open_container(method_call, 'a', "s");
        if (r < 0)
                return error_origin(r);

        while (!sd_bus_message_at_end(m, false)) {
                _c_cleanup_(c_freep) char *entry = NULL;
                const char *key, *value;

                r = sd_bus_message_read(m, "{ss}", &key, &value);
                if (r < 0)
                        return error_origin(r);

                r = asprintf(&entry, "%s=%s", key, value);
                if (r < 0)
                        return error_origin(-errno);

                r = sd_bus_message_append(method_call, "s", entry);
                if (r < 0)
                        return error_origin(r);
        }

        r = sd_bus_message_close_container(method_call);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_exit_container(m);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_call_async(manager->bus_regular, NULL, method_call, manager_set_environment_handler, NULL, -1);
        if (r < 0)
                return error_origin(r);

        return 0;
}

static int manager_on_message(sd_bus_message *m, void *userdata, sd_bus_error *error) {
        Manager *manager = userdata;
        const char *path, *suffix;
        int r = 0;

        path = sd_bus_message_get_path(m);
        if (!path)
                return 0;

        suffix = c_string_prefix(path, "/org/bus1/DBus/Name/");
        if (suffix) {
                if (sd_bus_message_is_signal(m, "org.bus1.DBus.Name", "Activate"))
                        r = manager_on_name_activate(manager, m, suffix);
        } else if (strcmp(path, "/org/bus1/DBus/Broker") == 0) {
                if (sd_bus_message_is_signal(m, "org.bus1.DBus.Broker", "SetActivationEnvironment"))
                        r = manager_on_set_activation_environment(manager, m);
        }

        return error_trace(r);
}

static int manager_load_service_file(Manager *manager, const char *path, NSSCache *nss_cache) {
        gchar *name = NULL, *user = NULL, *unit = NULL, *exec = NULL, **argv = NULL;
        gint argc = 0;
        _c_cleanup_(service_freep) Service *service = NULL;
        GKeyFile *f;
        CRBNode **slot, *parent;
        uid_t uid;
        int r;

        /*
         * There seems to be no trivial way to properly parse D-Bus service
         * files. Hence, we resort to glib GKeyFile to parse it as a Desktop
         * File compatible ini-file.
         *
         * Preferably, we'd not have the glib dependency here, but it does not
         * hurt much either. If anyone cares, feel free to provide `c-ini'.
         */

        f = g_key_file_new();

        if (!g_key_file_load_from_file(f, path, G_KEY_FILE_NONE, NULL)) {
                fprintf(stderr, "Cannot load service file '%s'\n", path);
                r = 0;
                goto exit;
        }

        name = g_key_file_get_string(f, "D-BUS Service", "Name", NULL);
        user = g_key_file_get_string(f, "D-BUS Service", "User", NULL);
        unit = g_key_file_get_string(f, "D-BUS Service", "SystemdService", NULL);
        exec = g_key_file_get_string(f, "D-BUS Service", "Exec", NULL);

        if (!name) {
                fprintf(stderr, "Missing name in service file '%s'\n", path);
                r = 0;
                goto exit;
        }

        if (!unit && !exec) {
                fprintf(stderr, "Missing exec or unit in service file '%s'\n", path);
                r = 0;
                goto exit;
        }

        if (exec) {
                GError *error = NULL;

                if (!g_shell_parse_argv(exec, &argc, &argv, &error)) {
                        fprintf(stderr, "Invalid exec '%s' in service file '%s': %s\n", exec, path, error->message);

                        g_error_free(error);
                        r = 0;
                        goto exit;
                }
        }

        if (user) {
                r = nss_cache_get_uid(nss_cache, &uid, NULL, user);
                if (r) {
                        if (r == NSS_CACHE_E_INVALID_NAME) {
                                fprintf(stderr, "Invalid user name '%s' in service file '%s'\n", user, path);
                                r = 0;
                                goto exit;
                        }
                }

        } else {
                uid = getuid();
        }

        slot = c_rbtree_find_slot(&manager->services_by_name, service_compare_by_name, name, &parent);
        if (slot) {
                r = service_new(&service, manager, name, slot, parent, unit, argc, argv, user, uid);
                if (r) {
                        r = error_trace(r);
                        goto exit;
                }
        } else {
                Service *old_service = c_container_of(parent, Service, rb_by_name);

                if (old_service->state == SERVICE_STATE_DEFUNCT) {
                        old_service->state = SERVICE_STATE_CURRENT;
                        r = service_update(old_service, unit, argc, argv, user, uid);
                        if (r)
                                r = error_trace(r);
                } else {
                        fprintf(stderr, "Ignoring duplicate name '%s' in service file '%s'\n", name, path);
                        r = 0;
                }
                goto exit;
        }

        service = NULL;
        r = 0;

exit:
        g_free(unit);
        g_free(user);
        g_free(name);
        g_free(exec);
        g_strfreev(argv);
        g_key_file_free(f);
        return r;
}

static int manager_load_service_dir(Manager *manager, const char *dirpath, NSSCache *nss_cache) {
        const char suffix[] = ".service";
        _c_cleanup_(c_closedirp) DIR *dir = NULL;
        struct dirent *de;
        char *path;
        size_t n;
        int r;

        dir = opendir(dirpath);
        if (!dir) {
                if (errno == ENOENT || errno == ENOTDIR)
                        return 0;
                else
                        return error_origin(-errno);
        }

        r = dirwatch_add(manager->dirwatch, dirpath);
        if (r)
                return error_fold(r);

        for (errno = 0, de = readdir(dir);
             de;
             errno = 0, de = readdir(dir)) {
                if (de->d_name[0] == '.')
                        continue;

                n = strlen(de->d_name);
                if (n <= strlen(suffix))
                        continue;
                if (strcmp(de->d_name + n - strlen(suffix), suffix))
                        continue;

                r = asprintf(&path, "%s/%s", dirpath, de->d_name);
                if (r < 0)
                        return error_origin(-ENOMEM);

                r = manager_load_service_file(manager, path, nss_cache);
                free(path);
                if (r)
                        return error_trace(r);
        }
        if (errno > 0)
                return error_origin(-errno);

        return 0;
}

static int manager_add_services(Manager *manager) {
        Service *service;
        int r;

        c_rbtree_for_each_entry(service, &manager->services, rb) {
                _c_cleanup_(c_freep) char *object_path = NULL;

                if (service->state != SERVICE_STATE_PENDING)
                        continue;

                r = asprintf(&object_path, "/org/bus1/DBus/Name/%s", service->id);
                if (r < 0)
                        return error_origin(-ENOMEM);

                r = sd_bus_call_method(manager->bus_controller,
                                       NULL,
                                       "/org/bus1/DBus/Broker",
                                       "org.bus1.DBus.Broker",
                                       "AddName",
                                       NULL,
                                       NULL,
                                       "osu",
                                       object_path,
                                       service->name,
                                       service->uid);
                if (r < 0)
                        return error_origin(r);

                service->state = SERVICE_STATE_CURRENT;
        }

        return 0;
}

static int manager_remove_services(Manager *manager) {
        Service *service, *service_safe;
        int r;

        c_rbtree_for_each_entry_safe(service, service_safe, &manager->services, rb) {
                _c_cleanup_(c_freep) char *object_path = NULL;

                if (service->state != SERVICE_STATE_DEFUNCT)
                        continue;

                r = asprintf(&object_path, "/org/bus1/DBus/Name/%s", service->id);
                if (r < 0)
                        return error_origin(-ENOMEM);

                r = sd_bus_call_method(manager->bus_controller,
                                       NULL,
                                       object_path,
                                       "org.bus1.DBus.Name",
                                       "Release",
                                       NULL,
                                       NULL,
                                       "");
                if (r < 0)
                        return error_origin(r);

                service_free(service);
        }

        return 0;
}

static int manager_load_standard_session_services(Manager *manager, NSSCache *nss_cache) {
        const char *suffix = "dbus-1/services";
        int r;

        /*
         * $XDG_RUNTIME_DIR/dbus-1/services is used in user-scope to
         * load transient units. dbus-daemon(1) actually creates this
         * path, we don't. It is incompatible with socket-activation of
         * dbus-daemon(1), so you must already be able to deal with
         * creating the directory yourself. But if the directory is
         * there, we load units from it.
         */
        {
                _c_cleanup_(c_freep) char *dirpath = NULL;
                const char *runtime_dir;

                runtime_dir = getenv("XDG_RUNTIME_DIR");
                if (!runtime_dir) {
                        fprintf(stderr, "Cannot figure out service runtime directory\n");
                } else {
                        r = asprintf(&dirpath, "%s/%s", runtime_dir, suffix);
                        if (r < 0)
                                return error_origin(-ENOMEM);

                        r = manager_load_service_dir(manager, dirpath, nss_cache);
                        if (r)
                                return error_trace(r);
                }
        }

        /*
         * $HOME/.local/share/dbus-1/services is used for user buses
         * additionally to the above mentioned directories. Note that
         * it can be modified via the XDG_DATA_HOME env-variable.
         */
        {
                _c_cleanup_(c_freep) char *data_home_dir = NULL;
                struct passwd *passwd;
                const char *dir;

                dir = getenv("XDG_DATA_HOME");
                if (dir) {
                        r = asprintf(&data_home_dir, "%s/%s", dir, suffix);
                        if (r < 0)
                                return error_origin(-ENOMEM);
                } else {
                        passwd = getpwuid(getuid());
                        if (passwd && passwd->pw_dir) {
                                r = asprintf(&data_home_dir, "%s/.local/share/%s", passwd->pw_dir, suffix);
                                if (r < 0)
                                        return error_origin(-ENOMEM);
                        }
                }
                if (!data_home_dir) {
                        fprintf(stderr, "Cannot figure out service home directory\n");
                } else {
                        r = manager_load_service_dir(manager, data_home_dir, nss_cache);
                        if (r)
                                return error_trace(r);
                }
        }

        /*
         * As last step, XDG_DATA_DIRS (or its default) are searched for
         * service files. ./dbus-1/services/ is appended to each path found in
         * XDG_DATA_DIRS.
         */
        {
                const char *data_dirs, *sep;
                size_t n;

                data_dirs = getenv("XDG_DATA_DIRS") ?:
                            "/usr/local/share:/usr/share";

                while (*data_dirs) {
                        sep = strchr(data_dirs, ':');
                        n = sep ? (size_t)(sep - data_dirs) : strlen(data_dirs);

                        if (n) {
                                _c_cleanup_(c_freep) char *dirpath = NULL;

                                r = asprintf(&dirpath, "%.*s/%s", (int)n, data_dirs, suffix);
                                if (r < 0)
                                        return error_origin(-ENOMEM);

                                r = manager_load_service_dir(manager, dirpath, nss_cache);
                                if (r)
                                        return error_trace(r);
                        }

                        data_dirs += n + !!sep;
                }
        }

        return 0;
}

static int manager_load_standard_system_services(Manager *manager, NSSCache *nss_cache) {
        static const char *default_data_dirs[] = {
                "/usr/local/share",
                "/usr/share",
                "/lib",
                NULL,
        };
        const char *suffix = "dbus-1/system-services";
        size_t i;
        int r;

        /*
         * In system scope, the default data directories are used. They
         * cannot be modified via env-variables!
         *
         * dbus-daemon(1) also supports /lib, which we don't. If there
         * is need, add it later.
         *
         * The order in which the directories are parsed follows the order
         * of dbus-daemon(1).
         */

        for (i = 0; default_data_dirs[i]; ++i) {
                _c_cleanup_(c_freep) char *dirpath = NULL;

                r = asprintf(&dirpath, "%s/%s", default_data_dirs[i], suffix);
                if (r < 0)
                        return error_origin(-ENOMEM);

                r = manager_load_service_dir(manager, dirpath, nss_cache);
                if (r)
                        return error_trace(r);
        }

        return 0;
}

static int manager_load_services(Manager *manager, ConfigRoot *config, NSSCache *nss_cache) {
        ConfigNode *cnode;
        int r;

        c_list_for_each_entry(cnode, &config->node_list, root_link) {
                switch (cnode->type) {
                case CONFIG_NODE_STANDARD_SESSION_SERVICEDIRS:
                        r = manager_load_standard_session_services(manager, nss_cache);
                        if (r)
                                return error_trace(r);

                        break;
                case CONFIG_NODE_STANDARD_SYSTEM_SERVICEDIRS:
                        r = manager_load_standard_system_services(manager, nss_cache);
                        if (r)
                                return error_trace(r);

                        break;
                case CONFIG_NODE_SERVICEDIR:
                        r = manager_load_service_dir(manager, cnode->servicedir.path, nss_cache);
                        if (r)
                                return error_trace(r);

                        break;
                default:
                        /* ignored */
                        break;
                }

        }

        return 0;
}

static int manager_parse_config(Manager *manager, ConfigRoot **rootp, NSSCache *nss_cache) {
        _c_cleanup_(config_parser_deinit) ConfigParser parser = CONFIG_PARSER_NULL(parser);
        _c_cleanup_(dirwatch_freep) Dirwatch *dirwatch = NULL;
        const char *configfile;
        ConfigNode *cnode;
        int r;

        r = dirwatch_new(&dirwatch);
        if (r)
                return error_fold(r);

        if (main_arg_configfile)
                configfile = main_arg_configfile;
        else if (main_arg_user_scope)
                configfile = "/usr/share/dbus-1/session.conf";
        else
                configfile = "/usr/share/dbus-1/system.conf";

        config_parser_init(&parser);

        r = config_parser_read(&parser, rootp, configfile, nss_cache, dirwatch);
        if (r) {
                if (r == CONFIG_E_INVALID)
                        return MANAGER_E_INVALID_CONFIG;

                return error_fold(r);
        }

        manager->dirwatch = dirwatch_free(manager->dirwatch);
        manager->dirwatch_src = sd_event_source_unref(manager->dirwatch_src);

        manager->dirwatch = dirwatch;
        dirwatch = NULL;

        r = sd_event_add_io(manager->event,
                            &manager->dirwatch_src,
                            dirwatch_get_fd(manager->dirwatch),
                            EPOLLIN,
                            manager_on_dirwatch,
                            manager);
        if (r)
                return error_origin(r);

        c_list_for_each_entry(cnode, &(*rootp)->node_list, root_link) {
                switch (cnode->type) {
                case CONFIG_NODE_USER:
                        if (cnode->user.valid) {
                                manager->uid = cnode->user.uid;
                                manager->gid = cnode->user.gid;
                        }

                        break;
                default:
                        /* ignored */
                        break;
                }

        }

        return 0;
}

static int manager_load_policy(Manager *manager, ConfigRoot *root, Policy *policy) {
        int r;

        r = policy_import(policy, root);
        if (r)
                return error_fold(r);

        policy_optimize(policy);

        return 0;
}

static int manager_add_listener(Manager *manager, Policy *policy, uint32_t *system_console_users, size_t n_system_console_users) {
        _c_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
        int r;

        r = sd_bus_message_new_method_call(manager->bus_controller,
                                           &m,
                                           NULL,
                                           "/org/bus1/DBus/Broker",
                                           "org.bus1.DBus.Broker",
                                           "AddListener");
        if (r < 0)
                return error_origin(r);

        r = sd_bus_message_append(m, "oh",
                                  "/org/bus1/DBus/Listener/0",
                                  manager->fd_listen);
        if (r < 0)
                return error_origin(r);

        r = policy_export(policy, m, system_console_users, n_system_console_users);
        if (r)
                return error_fold(r);

        r = sd_bus_call(manager->bus_controller, m, 0, NULL, NULL);
        if (r < 0)
                return error_origin(r);

        return 0;
}

static int manager_set_policy(Manager *manager, Policy *policy, uint32_t *system_console_users, size_t n_system_console_users) {
        _c_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
        int r;

        r = sd_bus_message_new_method_call(manager->bus_controller,
                                           &m,
                                           NULL,
                                           "/org/bus1/DBus/Listener/0",
                                           "org.bus1.DBus.Listener",
                                           "SetPolicy");
        if (r < 0)
                return error_origin(r);

        r = policy_export(policy, m, system_console_users, n_system_console_users);
        if (r)
                return error_fold(r);

        r = sd_bus_call(manager->bus_controller, m, 0, NULL, NULL);
        if (r < 0)
                return error_origin(r);

        return 0;
}

static int manager_reload_config(Manager *manager) {
        _c_cleanup_(config_root_freep) ConfigRoot *root = NULL;
        _c_cleanup_(policy_deinit) Policy policy = POLICY_INIT(policy);
        _c_cleanup_(nss_cache_deinit) NSSCache nss_cache = NSS_CACHE_INIT;
        _c_cleanup_(c_freep) uint32_t *system_console_users = NULL;
        size_t n_system_console_users;
        Service *service;
        int r, res;

        r = sd_notify(false, "RELOADING=1");
        if (r < 0)
                return error_origin(r);

        c_rbtree_for_each_entry(service, &manager->services, rb)
                service->state = SERVICE_STATE_DEFUNCT;

        r = nss_cache_populate(&nss_cache);
        if (r)
                goto out;

        r = manager_parse_config(manager, &root, &nss_cache);
        if (r)
                goto out;

        r = nss_cache_resolve_system_console_users(&nss_cache,
                                                   &system_console_users,
                                                   &n_system_console_users);
        if (r)
                return error_trace(r);

        r = manager_load_services(manager, root, &nss_cache);
        if (r)
                goto out;

        r = manager_load_policy(manager, root, &policy);
        if (r)
                goto out;

        switch (policy.apparmor_mode) {
        case CONFIG_APPARMOR_ENABLED: {
                bool enabled;

                /* XXX: See comments in manager_run() */

                r = bus_apparmor_is_enabled(&enabled);
                if (r)
                        return error_fold(r);

                if (enabled)
                        fprintf(stderr, "AppArmor enabled, but not supported. Ignoring.\n");

                policy.apparmor_mode = CONFIG_APPARMOR_DISABLED;
                break;
        }
        case CONFIG_APPARMOR_REQUIRED:
                fprintf(stderr, "AppArmor required, but not supported. Exiting.\n");

                r = sd_event_exit(manager->event, 0);
                if (r < 0)
                        return error_fold(r);

                return 0;
        }

        r = manager_remove_services(manager);
        if (r)
                goto out;

        r = manager_set_policy(manager, &policy, system_console_users, n_system_console_users);
        if (r)
                goto out;

        r = manager_add_services(manager);
        if (r)
                goto out;

out:
        res = sd_notify(false, "READY=1");
        if (res < 0)
                return error_origin(res);

        return error_trace(r);
}

static int manager_connect(Manager *manager) {
        int r;

        assert(!manager->bus_regular);

        if (main_arg_user_scope) {
                r = sd_bus_open_user(&manager->bus_regular);
                if (r < 0)
                        return error_origin(r);
        } else {
                r = sd_bus_open_system(&manager->bus_regular);
                if (r < 0)
                        return error_origin(r);
        }

        return 0;
}

static int bus_method_reload_config(sd_bus_message *message, void *userdata, sd_bus_error *error) {
        Manager *manager = userdata;
        int r;

        r = manager_reload_config(manager);
        if (r) {
                if (r == MANAGER_E_INVALID_CONFIG)
                        return sd_bus_reply_method_errorf(message, "org.bus1.DBus.Controller.Error.InvalidConfig", "Invalid configuration. Reload ignored.");
                else
                        return error_fold(r);
        }

        return sd_bus_reply_method_return(message, NULL);
}

const sd_bus_vtable manager_vtable[] = {
        SD_BUS_VTABLE_START(0),

        SD_BUS_METHOD("ReloadConfig", NULL, NULL, bus_method_reload_config, 0),

        SD_BUS_VTABLE_END
};

static int manager_run(Manager *manager) {
        _c_cleanup_(config_root_freep) ConfigRoot *root = NULL;
        _c_cleanup_(policy_deinit) Policy policy = POLICY_INIT(policy);
        _c_cleanup_(nss_cache_deinit) NSSCache nss_cache = NSS_CACHE_INIT;
        _c_cleanup_(c_freep) uint32_t *system_console_users = NULL;
        size_t n_system_console_users;
        int r, controller[2];

        r = nss_cache_populate(&nss_cache);
        if (r)
                return error_fold(r);

        r = manager_parse_config(manager, &root, &nss_cache);
        if (r)
                return error_trace(r);

        r = nss_cache_resolve_system_console_users(&nss_cache,
                                                   &system_console_users,
                                                   &n_system_console_users);
        if (r)
                return error_trace(r);

        r = manager_load_services(manager, root, &nss_cache);
        if (r)
                return error_trace(r);

        r = sd_notify(false, "READY=1");
        if (r < 0)
                return error_origin(r);

        r = manager_load_policy(manager, root, &policy);
        if (r)
                return error_trace(r);

        switch (policy.apparmor_mode) {
        case CONFIG_APPARMOR_ENABLED: {
                bool enabled;

                r = bus_apparmor_is_enabled(&enabled);
                if (r)
                        return error_fold(r);

                if (enabled)
                        fprintf(stderr, "AppArmor enabled, but not supported. Ignoring.\n");

                /* XXX: once the broker supports AppArmor, set this to DISABLED if and only if
                 *      it is disabled in the kernel. */
                policy.apparmor_mode = CONFIG_APPARMOR_DISABLED;
                break;
        }
        case CONFIG_APPARMOR_REQUIRED:
                fprintf(stderr, "AppArmor required, but not supported. Exiting.\n");

                /* XXX: once the broker supports AppArmor, set this to enabled if and only
                 *      if it is enabled in the kernel, and exit the launcher otherwise. */
                return 0;
        }

        assert(manager->fd_listen >= 0);

        r = socketpair(PF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, controller);
        if (r < 0)
                return error_origin(-errno);

        /* consumes FD controller[0] */
        r = sd_bus_set_fd(manager->bus_controller, controller[0], controller[0]);
        if (r < 0) {
                close(controller[0]);
                close(controller[1]);
                return error_origin(r);
        }

        /* consumes FD controller[1] */
        r = manager_fork(manager, controller[1]);
        if (r) {
                close(controller[1]);
                return error_trace(r);
        }

        r = sd_bus_add_object_vtable(manager->bus_controller, NULL, "/org/bus1/DBus/Controller", "org.bus1.DBus.Controller", manager_vtable, manager);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_add_filter(manager->bus_controller, NULL, manager_on_message, manager);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_start(manager->bus_controller);
        if (r < 0)
                return error_origin(r);

        r = manager_add_services(manager);
        if (r)
                return error_trace(r);

        r = manager_add_listener(manager, &policy, system_console_users, n_system_console_users);
        if (r)
                return error_trace(r);

        r = manager_connect(manager);
        if (r)
                return error_trace(r);

        r = sd_bus_attach_event(manager->bus_controller, manager->event, SD_EVENT_PRIORITY_NORMAL);
        if (r < 0)
                return error_origin(r);

        r = sd_bus_attach_event(manager->bus_regular, manager->event, SD_EVENT_PRIORITY_NORMAL);
        if (r < 0)
                return error_origin(r);

        if (manager->uid != (uint32_t)-1) {
                r = util_drop_permissions(manager->uid, manager->gid);
                if (r)
                        return error_fold(r);
        }

        r = sd_event_loop(manager->event);
        if (r < 0)
                return error_origin(r);
        else if (r > 0)
                return MAIN_FAILED;

        return 0;
}

static int open_log(void) {
        _c_cleanup_(c_closep) int fd = -1;
        struct sockaddr_un address = {
                .sun_family = AF_UNIX,
                .sun_path = "/run/systemd/journal/socket",
        };
        int r;

        fd = socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0);
        if (fd < 0)
                return error_origin(-errno);

        r = connect(fd,
                    (struct sockaddr *)&address,
                    offsetof(struct sockaddr_un, sun_path) + strlen(address.sun_path));
        if (r < 0)
                return error_origin(-errno);

        log_init_journal_consume(&main_log, fd);
        fd = -1;
        return 0;
}

static void help(void) {
        printf("%s [GLOBALS...] ...\n\n"
               "Linux D-Bus Message Broker Launcher\n\n"
               "  -h --help             Show this help\n"
               "     --version          Show package version\n"
               "     --audit            Enable audit support\n"
               "     --config-file PATH Specify path to configuration file\n"
               "     --scope SCOPE      Scope of message bus\n"
               , program_invocation_short_name);
}

static int parse_argv(int argc, char *argv[]) {
        enum {
                ARG_VERSION = 0x100,
                ARG_AUDIT,
                ARG_CONFIG,
                ARG_SCOPE,
        };
        static const struct option options[] = {
                { "help",               no_argument,            NULL,   'h'                     },
                { "version",            no_argument,            NULL,   ARG_VERSION             },
                { "audit",              no_argument,            NULL,   ARG_AUDIT               },
                { "config-file",        required_argument,      NULL,   ARG_CONFIG,             },
                { "scope",              required_argument,      NULL,   ARG_SCOPE               },
                {}
        };
        int c;

        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
                switch (c) {
                case 'h':
                        help();
                        return MAIN_EXIT;

                case ARG_VERSION:
                        printf("dbus-broker-launch %d\n", PACKAGE_VERSION);
                        return MAIN_EXIT;

                case ARG_AUDIT:
                        main_arg_audit = true;
                        break;

                case ARG_CONFIG:
                        main_arg_configfile = optarg;
                        break;

                case ARG_SCOPE:
                        if (!strcmp(optarg, "system")) {
                                main_arg_user_scope = false;
                        } else if (!strcmp(optarg, "user")) {
                                main_arg_user_scope = true;
                        } else {
                                fprintf(stderr, "%s: invalid message bus scope -- '%s'\n", program_invocation_name, optarg);
                                return MAIN_FAILED;
                        }
                        break;

                case '?':
                        /* getopt_long() prints warning */
                        return MAIN_FAILED;

                default:
                        return error_origin(-EINVAL);
                }
        }

        if (optind != argc) {
                fprintf(stderr, "%s: invalid arguments -- '%s'\n", program_invocation_name, argv[optind]);
                return MAIN_FAILED;
        }

        return 0;
}

static int run(void) {
        _c_cleanup_(manager_freep) Manager *manager = NULL;
        int r;

        r = manager_new(&manager);
        if (r)
                return error_trace(r);

        r = manager_listen_inherit(manager);
        if (r)
                return error_trace(r);

        r = manager_run(manager);
        r = error_trace(r);

        return r;

}

int main(int argc, char **argv) {
        sigset_t mask_new, mask_old;
        int r;

        r = open_log();
        if (r)
                goto exit;

        r = parse_argv(argc, argv);
        if (r)
                goto exit;

        sigemptyset(&mask_new);
        sigaddset(&mask_new, SIGCHLD);
        sigaddset(&mask_new, SIGTERM);
        sigaddset(&mask_new, SIGINT);
        sigaddset(&mask_new, SIGHUP);

        sigprocmask(SIG_BLOCK, &mask_new, &mask_old);
        r = run();
        sigprocmask(SIG_SETMASK, &mask_old, NULL);

exit:
        r = error_trace(r);
        if (r < 0)
                fprintf(stderr, "Exiting due to fatal error: %d\n", r);
        return (r == 0 || r == MAIN_EXIT) ? 0 : 1;
}
