diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2014-12-04 18:32:55 +0100 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2015-04-10 18:04:10 +0200 |
commit | 14684f858e4e5b0c00bc5878dd85cf2e0c68e053 (patch) | |
tree | 7da1c577ad4608005babf6476ca74b3d9982f9b6 | |
parent | a813b30b68dd57a3d6b93778ee95b43dee1cc9fd (diff) |
authority: add authorization and authentication daemonwip
WIP
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile.am | 32 | ||||
l--------- | src/authority/Makefile | 1 | ||||
-rw-r--r-- | src/authority/authorityctl.c | 242 | ||||
-rw-r--r-- | src/authority/authorityd-action.c | 153 | ||||
-rw-r--r-- | src/authority/authorityd-agent.c | 223 | ||||
-rw-r--r-- | src/authority/authorityd-authorization.c | 365 | ||||
-rw-r--r-- | src/authority/authorityd-dbus.c | 492 | ||||
-rw-r--r-- | src/authority/authorityd-factor.c | 73 | ||||
-rw-r--r-- | src/authority/authorityd-manager.c | 162 | ||||
-rw-r--r-- | src/authority/authorityd-subject.c | 451 | ||||
-rw-r--r-- | src/authority/authorityd.c | 67 | ||||
-rw-r--r-- | src/authority/authorityd.h | 197 | ||||
-rw-r--r-- | src/shared/authority-agent.c | 954 | ||||
-rw-r--r-- | src/shared/authority-agent.h | 34 |
15 files changed, 3448 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index 875ada5eb..e05ee3066 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ /TAGS /accelerometer /ata_id +/authorityctl /bootctl /build-aux /busctl @@ -56,6 +57,7 @@ /systemd-activate /systemd-analyze /systemd-ask-password +/systemd-authorityd /systemd-backlight /systemd-binfmt /systemd-bootchart diff --git a/Makefile.am b/Makefile.am index 0a5738944..ee44f0bbf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -212,6 +212,7 @@ AM_CPPFLAGS = \ -I $(top_srcdir)/src \ -I $(top_builddir)/src/shared \ -I $(top_srcdir)/src/shared \ + -I $(top_srcdir)/src/authority \ -I $(top_srcdir)/src/network \ -I $(top_srcdir)/src/login \ -I $(top_srcdir)/src/journal \ @@ -909,6 +910,8 @@ libsystemd_shared_la_SOURCES = \ src/shared/build.h \ src/shared/import-util.c \ src/shared/import-util.h \ + src/shared/authority-agent.c \ + src/shared/authority-agent.h \ src/shared/sysctl-util.c \ src/shared/sysctl-util.h @@ -5566,6 +5569,35 @@ endif endif # ------------------------------------------------------------------------------ +systemd_authorityd_SOURCES = \ + src/authority/authorityd.h \ + src/authority/authorityd.c \ + src/authority/authorityd-manager.c \ + src/authority/authorityd-dbus.c \ + src/authority/authorityd-authorization.c \ + src/authority/authorityd-factor.c \ + src/authority/authorityd-agent.c \ + src/authority/authorityd-subject.c \ + src/authority/authorityd-action.c + +systemd_authorityd_LDADD = \ + libsystemd-internal.la \ + libsystemd-shared.la + +rootlibexec_PROGRAMS += \ + systemd-authorityd + +authorityctl_SOURCES = \ + src/authority/authorityctl.c + +authorityctl_LDADD = \ + libsystemd-internal.la \ + libsystemd-shared.la + +rootbin_PROGRAMS += \ + authorityctl + +# ------------------------------------------------------------------------------ if ENABLE_RESOLVED systemd_resolved_SOURCES = \ src/resolve/resolved.c \ diff --git a/src/authority/Makefile b/src/authority/Makefile new file mode 120000 index 000000000..d0b0e8e00 --- /dev/null +++ b/src/authority/Makefile @@ -0,0 +1 @@ +../Makefile
\ No newline at end of file diff --git a/src/authority/authorityctl.c b/src/authority/authorityctl.c new file mode 100644 index 000000000..7b2e49df5 --- /dev/null +++ b/src/authority/authorityctl.c @@ -0,0 +1,242 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <getopt.h> +#include <locale.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include "authority-agent.h" +#include "build.h" +#include "bus-error.h" +#include "bus-util.h" +#include "pager.h" +#include "sd-bus.h" +#include "util.h" +#include "verbs.h" + +static bool arg_pager = true; +static bool arg_legend = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_user = false; + +static void pager_open_if_enabled(void) { + if (arg_pager) + pager_open(false); +} + +static int list_factors(int argc, char *argv[], void *userdata) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_bus_message_unref_ sd_bus_message *reply = NULL; + const char *factor, *busname; + sd_bus *bus = userdata; + unsigned int k = 0; + int r; + + pager_open_if_enabled(); + + r = sd_bus_call_method(bus, + "org.freedesktop.authority1", + "/org/freedesktop/authority1", + "org.freedesktop.authority1.Manager", + "ListFactors", + &error, &reply, + ""); + if (r < 0) { + log_error("Cannot list authentication factors: %s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(s)"); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_legend) + printf("%10s %-16s\n", "FACTOR", "BUSNAME"); + + while ((r = sd_bus_message_read(reply, "(s)", &factor, &busname)) > 0) { + printf("%10s %-16s\n", factor, busname); + ++k; + } + + if (arg_legend) + printf("\n%i factors listed.\n", k); + + return 0; +} + +static int list_agents(int argc, char *argv[], void *userdata) { + pager_open_if_enabled(); + + return 0; +} + +static int request_authorization(int argc, char *argv[], void *userdata) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + r = sd_bus_call_method(bus, + "org.freedesktop.authority1", + "/org/freedesktop/authority1", + "org.freedesktop.authority1.Manager", + "RequestBlockingAuthorization", + &error, NULL, + "a{sv}a{sv}ass", + /* subject */ + 2, + "pid", "i", getpid(), + "euid", "u", geteuid(), + /* action */ + 1, + "action-id", "s", "example.action", + /* flags */ + 1, "interactive", + /* unique */ + "unique"); + if (r < 0) { + log_error("Authorization request failed: %s", bus_error_message(&error, r)); + return r; + } + + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Query and control the authority daemon.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n\n" + "Commands:\n" + " list-factors List factors\n" + " list-agents List agents\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_USER, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "user", no_argument, NULL, ARG_USER }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hH:M:", options, NULL)) >= 0) { + switch (c) { + case 'h': + help(); + return 0; + + case ARG_VERSION: + puts(PACKAGE_STRING); + puts(SYSTEMD_FEATURES); + return 0; + + case ARG_NO_PAGER: + arg_pager = false; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_USER: + arg_user = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + } + + return 1; +} + +static int authorityctl_main(int argc, char *argv[], sd_bus *bus) { + const Verb verbs[] = { + { "list-factors", VERB_ANY, 1, 0, list_factors }, + { "list-agents", VERB_ANY, 1, 0, list_agents }, + { "request-authorization", VERB_ANY, 1, 0, request_authorization }, + {} + }; + + return dispatch_verb(argc, argv, verbs, bus); +} + +int main(int argc, char* argv[]) { + _cleanup_bus_close_unref_ sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bus_open_transport(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Cannot open bus connection: %m"); + goto finish; + } + + r = authorityctl_main(argc, argv, bus); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/authority/authorityd-action.c b/src/authority/authorityd-action.c new file mode 100644 index 000000000..37f7fcfb9 --- /dev/null +++ b/src/authority/authorityd-action.c @@ -0,0 +1,153 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "log.h" +#include "sd-bus.h" +#include "util.h" + +int action_new(Action **out) { + _cleanup_(action_unrefp) Action *a = NULL; + + assert_return(out, -EINVAL); + + a = new0(Action, 1); + if (!a) + return -ENOMEM; + + a->ref = 1; + + *out = a; + a = NULL; + return 0; +} + +Action *action_ref(Action *a) { + if (a) { + assert(a->ref > 0); + ++a->ref; + } + return a; +} + +Action *action_unref(Action *a) { + if (!a) + return NULL; + + assert(a->ref > 0); + + if (--a->ref > 0) + return NULL; + + free(a->action_id); + free(a); + + return NULL; +} + +static int action_parse_file(Action *a, int fd) { + /* TODO: parse action file @fd */ + + return 0; +} + +static int action_read_attribute(Action *a, const char *key, sd_bus_message *m) { + int r; + + assert(a); + assert(key); + assert(m); + + if (!strcmp(key, "action-id")) { + const char *str; + + r = sd_bus_message_read(m, "v", "s", &str); + if (r < 0) + return r; + + /* TODO: parse file */ + + r = free_and_strdup(&a->action_id, str); + if (r < 0) + return r; + } else if (!strcmp(key, "memfd")) { + int32_t fd; + + r = sd_bus_message_read(m, "v", "h", &fd); + if (r < 0) + return r; + + r = fcntl(fd, F_GET_SEALS); + if (r < 0) + return -errno; + + if (r != (F_SEAL_SEAL | F_SEAL_WRITE | F_SEAL_SHRINK | F_SEAL_GROW)) + return -EINVAL; + + r = action_parse_file(a, fd); + if (r < 0) + return r; + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return r; + } + + return 0; +} + +int action_read(Action *a, sd_bus_message *m) { + int r; + + assert(a); + assert(m); + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if (r <= 0) + return r; + + while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) { + const char *key; + + r = sd_bus_message_read(m, "s", &key); + if (r < 0) + return r; + + r = action_read_attribute(a, key, m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 1; +} diff --git a/src/authority/authorityd-agent.c b/src/authority/authorityd-agent.c new file mode 100644 index 000000000..fa5c68221 --- /dev/null +++ b/src/authority/authorityd-agent.c @@ -0,0 +1,223 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "hashmap.h" +#include "log.h" +#include "sd-bus.h" +#include "set.h" +#include "util.h" + +static Set *free_subject_set(Set *set) { + Subject *s; + + while ((s = set_steal_first(set))) + subject_unref(s); + + set_free(set); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, free_subject_set); + +static int agent_lost_fn(sd_bus_track *track, void *userdata) { + Agent *a = userdata; + + agent_free(a); + + return 0; +} + +int agent_new(Agent **out, Manager *m, const char *sender) { + _cleanup_(agent_freep) Agent *a = NULL; + int r; + + assert(out); + assert(m); + assert(sender); + + a = new0(Agent, 1); + if (!a) + return -ENOMEM; + + a->manager = m; + sprintf(a->id, "%" PRIu64, ++m->agent_ids); + + r = sd_bus_track_new(m->bus, &a->owner_track, agent_lost_fn, a); + if (r < 0) + return r; + + r = sd_bus_track_add_name(a->owner_track, sender); + if (r < 0) + return r; + + a->owner_busid = strdup(sender); + if (!a->owner_busid) + return -ENOMEM; + + r = hashmap_ensure_allocated(&m->agent_map, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(m->agent_map, a->id, a); + if (r < 0) + return r; + + *out = a; + a = NULL; + return 0; +} + +Agent *agent_free(Agent *a) { + if (!a) + return NULL; + + a->subjects = free_subject_set(a->subjects); + hashmap_remove_value(a->manager->agent_map, a->id, a); + free(a->owner_busid); + a->owner_track = sd_bus_track_unref(a->owner_track); + free(a); + + return NULL; +} + +#if 0 +static int agent_read_attribute(Agent *ag, const char *key, sd_bus_message *m) { + int r; + + assert(ag); + assert(key); + assert(m); + + if (!strcmp(key, "Language")) { + const char *str; + + r = sd_bus_message_read(m, "v", "s", &str); + if (r < 0) + return r; + + r = free_and_strdup(&ag->language, str); + if (r < 0) + return r; + } else if (!strcmp(key, "Fallback")) { + int v; + + r = sd_bus_message_read(m, "v", "b", &v); + if (r < 0) + return r; + + ag->fallback = !!v; + } else if (!strcmp(key, "Subjects")) { + _cleanup_(free_subject_setp) Set *set = NULL; + + set = set_new(NULL); + if (!set) + return -ENOMEM; + + r = sd_bus_message_enter_container(m, 'v', "aa{sv}"); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, 'a', "a{sv}"); + if (r < 0) + return r; + + for (;;) { + _cleanup_(subject_unrefp) Subject *s = NULL; + + r = subject_new(&s); + if (r < 0) + return r; + + r = subject_read(s, m); + if (r < 0) + return r; + if (r == 0) + break; + + /* TODO: make sure unprivileged agents cannot register for arbitrary subjects */ + + r = set_put(set, s); + if (r < 0) + return r; + + s = NULL; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + free_subject_set(ag->subjects); + ag->subjects = set; + set = NULL; + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return r; + } + + return 0; +} + +int agent_read(Agent *ag, sd_bus_message *m) { + int r; + + assert(ag); + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if (r <= 0) + return r; + + while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) { + const char *key; + + r = sd_bus_message_read(m, "s", &key); + if (r < 0) + return r; + + r = agent_read_attribute(ag, key, m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 1; +} +#endif diff --git a/src/authority/authorityd-authorization.c b/src/authority/authorityd-authorization.c new file mode 100644 index 000000000..91da4151c --- /dev/null +++ b/src/authority/authorityd-authorization.c @@ -0,0 +1,365 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "hashmap.h" +#include "log.h" +#include "sd-bus.h" +#include "sd-event.h" +#include "util.h" + +int authorization_new(Authorization **out, Manager *m) { + _cleanup_(authorization_freep) Authorization *authorization = NULL; + int r; + + assert(out); + assert(m); + + authorization = new0(Authorization, 1); + if (!authorization) + return -ENOMEM; + + authorization->manager = m; + authorization->uid = UID_INVALID; + sprintf(authorization->id, "%" PRIu64, ++m->authorization_ids); + + r = hashmap_ensure_allocated(&m->authorization_map, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(m->authorization_map, authorization->id, authorization); + if (r < 0) + return r; + + *out = authorization; + authorization = NULL; + return 0; +} + +Authorization *authorization_free(Authorization *authorization) { + if (!authorization) + return NULL; + + authorization_stop(authorization, -ECANCELED); + + hashmap_remove_value(authorization->manager->authorization_map, authorization->id, authorization); + + assert(hashmap_isempty(authorization->transaction_map)); + + authorization->action = action_unref(authorization->action); + authorization->subject = subject_unref(authorization->subject); + authorization->transaction_map = hashmap_free(authorization->transaction_map); + free(authorization); + + return NULL; +} + +static int authorization_schedule_interactive(Authorization *authorization) +{ + unsigned int k = 0; + Transaction *trans; + Iterator i; + Factor *factor; + int r; + + /* SELECT AGENT */ + + HASHMAP_FOREACH(factor, authorization->manager->factor_map, i) { + /*r = factor_start(factor, &trans, authorization, true); + if (r < 0) + return r; + if (r > 0) + ++k;*/ + } + + return k > 0; +} + +static int authorization_schedule_non_interactive(Authorization *authorization) +{ + unsigned int k = 0; + + /* skip if UID is selectable */ + if (authorization->uid == UID_INVALID) + return 0; + +/* + Iterator i; + Factor *factor; + int r; + + HASHMAP_FOREACH(factor, auth->manager->factor_map, i) { + r = factor_attach(factor, auth, false); + if (r < 0) + return r; + if (r > 0) + ++k; + } +*/ + return k > 0; +} + +static int authorization_run_rules(Authorization *authorization) +{ + /* skip if UID is selectable */ + if (authorization->uid == UID_INVALID) + return 0; + + /* no static rules are supported right now */ + return 0; +} + +static int authorization_run_temporary(Authorization *authorization) +{ + /* skip if UID is selectable */ + if (authorization->uid == UID_INVALID) + return 0; + + /* no temporary authorizations are supported right now */ + return 0; +} + +static int authorization_run_fn(sd_event_source *src, void *userdata) { + Authorization *authorization = userdata; + int r; + + assert(authorization->started); + + if (authorization->stopped) + return 0; + + /* + * Authorization + * When we run an authorization, we have the subject and action fully + * initialized and will go through the following steps (in order): + * - Check for any temporary authorizations that have been granted by + * a previous request. No environment changes are taken into + * account. This means, if the temporary authorization was granted + * by a different action, then we will *not* run through the rules + * again. If an action allows temporary authorizations, it must be + * aware of that. + * - If no temporary authorization is granted, we run through the + * configured rules to see whether a static authorization is + * granted. + * - If no rule matches, we ask the authentication factors to run + * non-interactive authentication against the subject. + * - If non-interactive authentication didn't succeed, we run + * interactive authentication, iff the request allows it. + * + * If, at any step, authorization is granted, we immediately bail out + * and return success. This means, you must never rely on negative + * results in any of these steps. For instance, a non-interactive + * authentication factor cannot prevent an authentication, if a static + * rule already granted the authorization. Same is true for negative + * results: We always continue with the next step, if all previous + * steps denied authorization. + */ + + switch (authorization->run_state) { + case AUTHORIZATION_RUN_TEMPORARY: + r = authorization_run_temporary(authorization); + if (r != 0) + break; + + ++authorization->run_state; + /* fallthrough */ + case AUTHORIZATION_RUN_RULES: + r = authorization_run_rules(authorization); + if (r != 0) + break; + + ++authorization->run_state; + /* fallthrough */ + case AUTHORIZATION_RUN_NON_INTERACTIVE: + r = authorization_schedule_non_interactive(authorization); + if (r < 0) + break; + if (r > 0) { + r = 0; + break; + } + + ++authorization->run_state; + /* fallthrough */ + case AUTHORIZATION_RUN_INTERACTIVE: + r = authorization_schedule_interactive(authorization); + if (r < 0) + break; + if (r > 0) { + r = 0; + break; + } + + ++authorization->run_state; + /* fallthrough */ + default: + /* no authorization was granted */ + r = -EPERM; + break; + } + + if (r != 0) + authorization_stop(authorization, r); + + return 0; +} + +static int authorization_lost_fn(sd_bus_track *track, void *userdata) { + authorization_stop(userdata, -ECANCELED); + return 0; +} + +int authorization_start(Authorization *authorization, sd_bus_message *m, const char *block_unique) { + const char *sender; + int r; + + assert(authorization); + assert(m); + + if (authorization->stopped) + return -ECANCELED; + if (authorization->started) + return -EALREADY; + + sender = sd_bus_message_get_sender(m); + if (!sender) + return -EINVAL; + + /* track sender so we don't leak authorization objects on disconnect */ + r = sd_bus_track_new(authorization->manager->bus, &authorization->owner_track, authorization_lost_fn, authorization); + if (r < 0) + return r; + + r = sd_bus_track_add_name(authorization->owner_track, sender); + if (r < 0) + return r; + + authorization->owner_busid = strdup(sender); + if (!authorization->owner_busid) + return -ENOMEM; + + /* on blocking requests, we track the message so we can send a reply */ + if (block_unique) { + authorization->owner_unique = strjoin(sender, "/", block_unique, NULL); + if (!authorization->owner_unique) + return -ENOMEM; + + authorization->owner_msg = sd_bus_message_ref(m); + + r = hashmap_ensure_allocated(&authorization->manager->authorization_by_unique, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(authorization->manager->authorization_by_unique, authorization->owner_unique, authorization); + if (r < 0) + return r; + } + + /* + * Now that the authorization object is fully prepared, we schedule the + * execution function and notify dbus listeners. We defer the execution + * so dbus events are always sent in correct order for non-blocking and + * blocking calls. + */ + + /* defer authorization so the dbus events are correctly ordered */ + r = sd_event_add_defer(authorization->manager->event, &authorization->run_src, authorization_run_fn, authorization); + if (r < 0) + return r; + + authorization->started = true; + + r = manager_bus_emit_authorization_new(authorization->manager, authorization); + if (r < 0) { + authorization_stop(authorization, r); + return r; + } + + authorization->live = true; + + return 0; +} + +void authorization_stop(Authorization *authorization, int ret) { + Transaction *transaction; + int r; + + assert(authorization); + + if (authorization->stopped) + return; + + authorization->stopped = true; + + /* drop transactions */ + while ((transaction = hashmap_first(authorization->transaction_map))) + ;//transaction_free(transaction); + + /* notify bus listeners */ + if (authorization->live) { + r = manager_bus_emit_authorization_removed(authorization->manager, authorization); + if (r < 0) + log_warning_errno(r, "Cannot emit AuthorizationRemoved signal: %m"); + } + + /* send reply for blocking requests */ + if (authorization->owner_unique) { + if (authorization->live) { + if (ret < 0) + sd_bus_reply_method_errno(authorization->owner_msg, ret, NULL); + else + sd_bus_reply_method_return(authorization->owner_msg, NULL); + } + + authorization->owner_msg = sd_bus_message_unref(authorization->owner_msg); + + hashmap_remove_value(authorization->manager->authorization_by_unique, authorization->owner_unique, authorization); + free(authorization->owner_unique); + authorization->owner_unique = NULL; + } + + authorization->run_src = sd_event_source_unref(authorization->run_src); + free(authorization->owner_busid); + authorization->owner_busid = NULL; + authorization->owner_track = sd_bus_track_unref(authorization->owner_track); + authorization->agent = NULL; + + authorization->live = false; +} + +Authorization *authorization_lookup(Manager *m, const char *sender, const char *block_unique) { + _cleanup_free_ char *key = NULL; + + assert(m); + assert(sender); + assert(block_unique); + + key = strjoin(sender, "/", block_unique, NULL); + if (!key) + return NULL; + + return hashmap_get(m->authorization_by_unique, key); +} diff --git a/src/authority/authorityd-dbus.c b/src/authority/authorityd-dbus.c new file mode 100644 index 000000000..e923ab462 --- /dev/null +++ b/src/authority/authorityd-dbus.c @@ -0,0 +1,492 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "hashmap.h" +#include "log.h" +#include "sd-bus.h" +#include "strv.h" +#include "util.h" + +static char *factor_bus_path(Factor *factor) { + char *path; + int r; + + assert(factor); + + r = sd_bus_path_encode("/org/freedesktop/authority1/factor", factor->id, &path); + if (r < 0) + return NULL; + + return path; +} + +static int factor_bus_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *id = NULL; + Manager *m = userdata; + Factor *factor; + int r; + + r = sd_bus_path_decode(path, "/org/freedesktop/authority1/factor", &id); + if (r <= 0) + return r; + + factor = hashmap_get(m->factor_map, id); + if (!factor) + return 0; + + *found = factor; + return 1; +} + +static int factor_bus_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + Factor *factor; + Iterator i; + int r; + + HASHMAP_FOREACH(factor, m->factor_map, i) { + char *s; + + s = factor_bus_path(factor); + if (!s) + return -ENOMEM; + + r = strv_consume(&l, s); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + return 1; +} + +static const sd_bus_vtable factor_bus_vtable[] = { + SD_BUS_VTABLE_START(0), + //SD_BUS_PROPERTY("UID", "u", authorization_bus_get_uid, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + //SD_BUS_PROPERTY("Subject", "...", authorization_bus_get_subject, 0, SD_BUS_VTABLE_PROPERTY_CONST), + //SD_BUS_PROPERTY("Action", "...", authorization_bus_get_action, 0, SD_BUS_VTABLE_PROPERTY_CONST), + //SD_BUS_PROPERTY("PrimaryAgent", "o", authorization_bus_get_primary_agent, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + //SD_BUS_PROPERTY("Agents", "ao", authorization_bus_get_agents, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + //SD_BUS_PROPERTY("Factors", "ao", authorization_bus_get_factors, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + //SD_BUS_SIGNAL("Done", "i", 0), + SD_BUS_VTABLE_END +}; + +static int authorization_bus_cancel(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + Authorization *authorization = userdata; + const char *sender; + + sender = sd_bus_message_get_sender(m); + if (!sender || !streq_ptr(sender, authorization->owner_busid)) + return -EPERM; + + authorization_stop(authorization, -ECANCELED); + authorization_free(authorization); + + return 0; +} + +static char *authorization_bus_path(Authorization *authorization) { + char *path; + int r; + + assert(authorization); + + r = sd_bus_path_encode("/org/freedesktop/authority1/authorization", authorization->id, &path); + if (r < 0) + return NULL; + + return path; +} + +static int authorization_bus_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *id = NULL; + Manager *m = userdata; + Authorization *authorization; + int r; + + r = sd_bus_path_decode(path, "/org/freedesktop/authority1/authorization", &id); + if (r <= 0) + return r; + + authorization = hashmap_get(m->authorization_map, id); + if (!authorization) + return 0; + + *found = authorization; + return 1; +} + +static int authorization_bus_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + Authorization *authorization; + Iterator i; + int r; + + HASHMAP_FOREACH(authorization, m->authorization_map, i) { + char *s; + + s = authorization_bus_path(authorization); + if (!s) + return -ENOMEM; + + r = strv_consume(&l, s); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + return 1; +} + +static const sd_bus_vtable authorization_bus_vtable[] = { + SD_BUS_VTABLE_START(0), + //SD_BUS_PROPERTY("UID", "u", authorization_bus_get_uid, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + //SD_BUS_PROPERTY("Subject", "...", authorization_bus_get_subject, 0, SD_BUS_VTABLE_PROPERTY_CONST), + //SD_BUS_PROPERTY("Action", "...", authorization_bus_get_action, 0, SD_BUS_VTABLE_PROPERTY_CONST), + //SD_BUS_PROPERTY("PrimaryAgent", "o", authorization_bus_get_primary_agent, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + //SD_BUS_PROPERTY("Agents", "ao", authorization_bus_get_agents, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + //SD_BUS_PROPERTY("Factors", "ao", authorization_bus_get_factors, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_METHOD("Cancel", "i", NULL, authorization_bus_cancel, 0), + //SD_BUS_SIGNAL("Done", "i", 0), + SD_BUS_VTABLE_END +}; + +#if 0 +static int manager_bus_lost_agent(sd_bus_track *track, void *userdata) { + Agent *ag = userdata; + + agent_free(ag); + return 0; +} + +static int manager_bus_new_agent(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + _cleanup_(agent_freep) Agent *ag = NULL; + Manager *manager = userdata; + int r; + + /* prevent unbound allocations so each peer is only allowed a single agent */ + if (hashmap_get(manager->ag_by_owner, sd_bus_message_get_sender(m))) + return -EALREADY; + + r = agent_new(&ag, manager); + if (r < 0) + return r; + + /* track owner */ + + ag->owner_id = strdup(sd_bus_message_get_sender(m)); + if (!ag->owner_id) + return -ENOMEM; + + r = sd_bus_track_new(bus, &ag->owner_track, manager_bus_lost_agent, ag); + if (r < 0) + return r; + + r = sd_bus_track_add_sender(ag->owner_track, m); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&manager->ag_by_owner, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(manager->ag_by_owner, ag->owner_id, ag); + if (r < 0) + return r; + + /* set initial attributes */ + + r = agent_read(ag, m); + if (r < 0) + return r; + + return sd_bus_reply_method_return(m, NULL); +} +#endif + +static int manager_bus_parse_authorization_request(Manager *manager, Authorization **out, sd_bus *bus, sd_bus_message *m) { + _cleanup_(authorization_freep) Authorization *authorization = NULL; + _cleanup_(subject_unrefp) Subject *subject = NULL; + _cleanup_(action_unrefp) Action *action = NULL; + const char *flag; + int r; + + /* parse subject */ + + r = subject_new(&subject); + if (r < 0) + return r; + + r = subject_read(subject, m); + if (r < 0) + return r; + + /* parse action */ + + r = action_new(&action); + if (r < 0) + return r; + + r = action_read(action, m); + if (r < 0) + return r; + + /* create authorization object */ + + r = authorization_new(&authorization, manager); + if (r < 0) + return r; + + authorization->subject = subject; + subject = NULL; + authorization->action = action; + action = NULL; + + /* parse flags */ + + r = sd_bus_message_enter_container(m, 'a', "s"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(m, "s", &flag)) > 0) { + if (!strcmp(flag, "interactive")) + authorization->interactive = true; + else + return -EOPNOTSUPP; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + *out = authorization; + authorization = NULL; + return 0; +} + +static int manager_bus_request_authorization(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + _cleanup_(authorization_freep) Authorization *authorization = NULL; + _cleanup_free_ char *path = NULL; + Manager *manager = userdata; + int r; + + r = manager_bus_parse_authorization_request(manager, &authorization, bus, m); + if (r < 0) + return r; + + path = authorization_bus_path(authorization); + if (!path) + return -ENOMEM; + + r = authorization_start(authorization, m, NULL); + if (r < 0) + return r; + + authorization = NULL; + + return sd_bus_reply_method_return(m, "o", path); +} + +int manager_bus_emit_authorization_new(Manager *m, Authorization *authorization) +{ + _cleanup_free_ char *path = NULL; + + assert(m); + assert(authorization); + + path = authorization_bus_path(authorization); + if (!path) + return -ENOMEM; + + return sd_bus_emit_signal(m->bus, + "/org/freedesktop/authority1", + "org.freedesktop.authority1.Manager", + "AuthorizationNew", + "o", + path); +} + +int manager_bus_emit_authorization_removed(Manager *m, Authorization *authorization) +{ + _cleanup_free_ char *path = NULL; + + assert(m); + assert(authorization); + + path = authorization_bus_path(authorization); + if (!path) + return -ENOMEM; + + return sd_bus_emit_signal(m->bus, + "/org/freedesktop/authority1", + "org.freedesktop.authority1.Manager", + "AuthorizationRemoved", + "o", + path); +} + +static int manager_bus_request_blocking_authorization(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + _cleanup_(authorization_freep) Authorization *authorization = NULL; + Manager *manager = userdata; + const char *unique; + int r; + + r = manager_bus_parse_authorization_request(manager, &authorization, bus, m); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "s", &unique); + if (r < 0) + return r; + if (strlen(unique) > 255) + return -EINVAL; + + r = authorization_start(authorization, m, unique); + if (r < 0) + return r; + + authorization = NULL; + + /* reply is sent once the authorization finished */ + return 1; +} + +static int manager_bus_cancel_blocking_authorization(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + const char *sender, *unique; + Authorization *authorization; + int r; + + sender = sd_bus_message_get_sender(m); + if (!sender) + return -EINVAL; + + r = sd_bus_message_read(m, "s", &unique); + if (r < 0) + return r; + if (strlen(unique) > 255) + return -EINVAL; + + authorization = authorization_lookup(manager, sender, unique); + if (!authorization) + return -ENOENT; + + authorization_stop(authorization, -ECANCELED); + authorization_free(authorization); + + return 0; +} + +static const sd_bus_vtable manager_bus_vtable[] = { + SD_BUS_VTABLE_START(0), + + /* Factor */ + //SD_BUS_METHOD("NewFactor", "a{sv}", "o", manager_bus_new_factor, 0), + //SD_BUS_METHOD("RemoveFactor", "o", NULL, manager_bus_remove_factor, 0), + //SD_BUS_METHOD("ListFactors", NULL, "a(s)", manager_bus_list_factors, SD_BUS_VTABLE_UNPRIVILEGED), + + /* Agent */ + //SD_BUS_METHOD("NewAgent", "a{sv}", "o", manager_bus_new_agent, SD_BUS_VTABLE_UNPRIVILEGED), + //SD_BUS_METHOD("RemoveAgent", "o", NULL, manager_bus_remove_agent, SD_BUS_VTABLE_UNPRIVILEGED), + + /* Authorization */ + SD_BUS_METHOD("RequestAuthorization", "a{sv}a{sv}ass", NULL, manager_bus_request_authorization, 0), + + /* Convenience API */ + SD_BUS_METHOD("RequestBlockingAuthorization", "a{sv}a{sv}ass", NULL, manager_bus_request_blocking_authorization, 0), + SD_BUS_METHOD("CancelBlockingAuthorization", "s", NULL, manager_bus_cancel_blocking_authorization, 0), + + SD_BUS_VTABLE_END +}; + +int manager_bus_init(Manager *m) { + int r; + + /* manager */ + + r = sd_bus_add_object_vtable(m->bus, + NULL, + "/org/freedesktop/authority1", + "org.freedesktop.authority1.Manager", + manager_bus_vtable, + m); + if (r < 0) + return r; + + r = sd_bus_add_object_manager(m->bus, + NULL, + "/org/freedesktop/authority1"); + if (r < 0) + return r; + + /* authorization */ + + r = sd_bus_add_fallback_vtable(m->bus, + NULL, + "/org/freedesktop/authority1/authorization", + "org.freedesktop.authority1.Authorization", + authorization_bus_vtable, + authorization_bus_find, + m); + if (r < 0) + return r; + + r = sd_bus_add_node_enumerator(m->bus, + NULL, + "/org/freedesktop/authority1/authorization", + authorization_bus_enumerate, + m); + if (r < 0) + return r; + + /* factor */ + + r = sd_bus_add_fallback_vtable(m->bus, + NULL, + "/org/freedesktop/authority1/factor", + "org.freedesktop.authority1.Factor", + factor_bus_vtable, + factor_bus_find, + m); + if (r < 0) + return r; + + r = sd_bus_add_node_enumerator(m->bus, + NULL, + "/org/freedesktop/authority1/factor", + factor_bus_enumerate, + m); + if (r < 0) + return r; + + return 0; +} diff --git a/src/authority/authorityd-factor.c b/src/authority/authorityd-factor.c new file mode 100644 index 000000000..d440b5671 --- /dev/null +++ b/src/authority/authorityd-factor.c @@ -0,0 +1,73 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "hashmap.h" +#include "log.h" +#include "util.h" + +int factor_new(Factor **out, Manager *m) { + _cleanup_(factor_freep) Factor *f = NULL; + int r; + + assert_return(out, -EINVAL); + + f = new0(Factor, 1); + if (!f) + return -ENOMEM; + + f->manager = m; + sprintf(f->id, "%" PRIu64, ++m->factor_ids); + + r = hashmap_ensure_allocated(&m->factor_map, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(m->factor_map, f->id, f); + if (r < 0) + return r; + + *out = f; + f = NULL; + return 0; +} + +Factor *factor_free(Factor *f) { + if (!f) + return NULL; + + hashmap_remove_value(f->manager->factor_map, f->id, f); + free(f); + + return NULL; +} + +int factor_attach(Factor *f, Authorization *auth, bool interactive) { + return -EINVAL; +} + +void factor_detach(Factor *f, Authorization *auth) { +} diff --git a/src/authority/authorityd-manager.c b/src/authority/authorityd-manager.c new file mode 100644 index 000000000..b8da984dd --- /dev/null +++ b/src/authority/authorityd-manager.c @@ -0,0 +1,162 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "hashmap.h" +#include "log.h" +#include "sd-bus.h" +#include "sd-event.h" +#include "sd-path.h" +#include "strv.h" +#include "util.h" + +static const struct { + uint64_t type; + const char *suffix; +} manager_dirs[] = { + { SD_PATH_USER_CONFIGURATION, "systemd/authority" }, + { -1, "/etc/systemd/authority" }, + { SD_PATH_USER_RUNTIME, "systemd/authority" }, + { -1, "/run/systemd/authority" }, + { SD_PATH_USER_LIBRARY_PRIVATE, "systemd/authority" }, + { -1, "/usr/lib/systemd/authority" }, +}; + +int manager_new(Manager **out, bool system_level) { + _cleanup_(manager_freep) Manager *m = NULL; + size_t i, pos; + int r; + + assert(out); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + r = sd_event_set_watchdog(m->event, true); + if (r < 0) + return r; + + r = sigprocmask_many(SIG_BLOCK, SIGTERM, SIGQUIT, SIGINT, -1); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, SIGQUIT, NULL, NULL); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + if (r < 0) + return r; + + m->dirs = new0(char*, ELEMENTSOF(manager_dirs) + 1); + if (!m->dirs) + return -ENOMEM; + + for (i = 0, pos = 0; i < ELEMENTSOF(manager_dirs); ++i) { + if (manager_dirs[i].type == (uint64_t)-1) { + m->dirs[pos] = strdup(manager_dirs[i].suffix); + if (!m->dirs[pos++]) + return -ENOMEM; + } else if (!system_level) { + r = sd_path_home(manager_dirs[i].type, manager_dirs[i].suffix, &m->dirs[pos++]); + if (r < 0) + return r; + } + } + + if (system_level) + r = sd_bus_default_system(&m->bus); + else + r = sd_bus_default_user(&m->bus); + if (r < 0) + return log_error_errno(r, "Cannot connect to bus: %m"); + + r = manager_bus_init(m); + if (r < 0) + return log_error_errno(r, "Cannot initialize bus API: %m"); + + r = sd_bus_request_name(m->bus, "org.freedesktop.authority1", 0); + if (r < 0) + return log_error_errno(r, "Cannot register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return r; + + *out = m; + m = NULL; + return 0; +} + +Manager *manager_free(Manager *m) { + Authorization *authorization; + Factor *factor; + Agent *agent; + + if (!m) + return NULL; + + while ((authorization = hashmap_first(m->authorization_map))) + authorization_free(authorization); + + while ((factor = hashmap_first(m->factor_map))) + factor_free(factor); + + while ((agent = hashmap_first(m->agent_map))) + agent_free(agent); + + assert(hashmap_isempty(m->authorization_by_unique)); + assert(hashmap_isempty(m->agent_by_owner)); + assert(hashmap_isempty(m->authorization_map)); + assert(hashmap_isempty(m->factor_map)); + assert(hashmap_isempty(m->agent_map)); + + m->authorization_by_unique = hashmap_free(m->authorization_by_unique); + m->agent_by_owner = hashmap_free(m->agent_by_owner); + m->authorization_map = hashmap_free(m->authorization_map); + m->factor_map = hashmap_free(m->factor_map); + m->agent_map = hashmap_free(m->agent_map); + m->dirs = strv_free(m->dirs); + m->bus = sd_bus_unref(m->bus); + m->event = sd_event_unref(m->event); + free(m); + + return NULL; +} + +int manager_run(Manager *m) { + assert(m); + + return sd_event_loop(m->event); +} diff --git a/src/authority/authorityd-subject.c b/src/authority/authorityd-subject.c new file mode 100644 index 000000000..337d7eeab --- /dev/null +++ b/src/authority/authorityd-subject.c @@ -0,0 +1,451 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "bus-util.h" +#include "log.h" +#include "sd-bus.h" +#include "strv.h" +#include "util.h" + +assert_cc(sizeof(uid_t) >= 4); +assert_cc(sizeof(gid_t) >= 4); +assert_cc(sizeof(pid_t) >= 4); + +int subject_new(Subject **out) { + _cleanup_(subject_unrefp) Subject *s = NULL; + + assert_return(out, -EINVAL); + + s = new0(Subject, 1); + if (!s) + return -ENOMEM; + + s->ref = 1; + s->euid = UID_INVALID; + s->fsuid = UID_INVALID; + s->egid = GID_INVALID; + s->fsgid = GID_INVALID; + + *out = s; + s = NULL; + return 0; +} + +Subject *subject_ref(Subject *s) { + if (s) { + assert(s->ref > 0); + ++s->ref; + } + return s; +} + +Subject *subject_unref(Subject *s) { + if (!s) + return NULL; + + assert(s->ref > 0); + + if (--s->ref > 0) + return NULL; + + strv_free(s->well_known_names); + free(s->unique_name); + free(s->session_id); + free(s->supplementary_gids); + free(s); + + return NULL; +} + +static int subject_read_attribute(Subject *s, const char *key, sd_bus_message *m) { + int r; + + assert(s); + assert(key); + assert(m); + + if (!strcmp(key, "pid")) { + int32_t pid; + + r = sd_bus_message_read(m, "v", "i", &pid); + if (r < 0) + return r; + if (pid <= 0) + return -EINVAL; + + s->pid = pid; + } else if (!strcmp(key, "tid")) { + int32_t tid; + + r = sd_bus_message_read(m, "v", "i", &tid); + if (r < 0) + return r; + if (tid <= 0) + return -EINVAL; + + s->tid = tid; + } else if (!strcmp(key, "euid")) { + uint32_t euid; + + r = sd_bus_message_read(m, "v", "u", &euid); + if (r < 0) + return r; + if (euid == UID_INVALID) + return -EINVAL; + + s->euid = euid; + } else if (!strcmp(key, "fsuid")) { + uint32_t fsuid; + + r = sd_bus_message_read(m, "v", "u", &fsuid); + if (r < 0) + return r; + if (fsuid == UID_INVALID) + return -EINVAL; + + s->fsuid = fsuid; + } else if (!strcmp(key, "egid")) { + uint32_t egid; + + r = sd_bus_message_read(m, "v", "u", &egid); + if (r < 0) + return r; + if (egid == GID_INVALID) + return -EINVAL; + + s->egid = egid; + } else if (!strcmp(key, "fsgid")) { + uint32_t fsgid; + + r = sd_bus_message_read(m, "v", "u", &fsgid); + if (r < 0) + return r; + if (fsgid == GID_INVALID) + return -EINVAL; + + s->fsgid = fsgid; + } else if (!strcmp(key, "supplementary-gids")) { + const uint32_t *gids; + size_t i, n, size; + gid_t *t; + + r = sd_bus_message_enter_container(m, 'v', "au"); + if (r < 0) + return r; + + r = sd_bus_message_read_array(m, 'u', (const void**)&gids, &size); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + n = size / sizeof(*gids); + for (i = 0; i < n; ++i) + if (gids[i] == GID_INVALID) + return -EINVAL; + + t = new(gid_t, n); + if (!t) + return -ENOMEM; + + for (i = 0; i < n; ++i) + t[i] = gids[i]; + + free(s->supplementary_gids); + s->supplementary_gids = t; + s->n_supplementary_gids = n; + } else if (!strcmp(key, "session-id")) { + const char *sid; + + r = sd_bus_message_read(m, "v", "s", &sid); + if (r < 0) + return r; + + r = free_and_strdup(&s->session_id, sid); + if (r < 0) + return r; + } else if (!strcmp(key, "unique-name")) { + const char *str; + + r = sd_bus_message_read(m, "v", "s", &str); + if (r < 0) + return r; + + r = free_and_strdup(&s->unique_name, str); + if (r < 0) + return r; + } else if (!strcmp(key, "well-known-names")) { + _cleanup_strv_free_ char **strv = NULL; + + r = sd_bus_message_enter_container(m, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(m, &strv); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + strv_free(s->well_known_names); + s->well_known_names = strv; + strv = NULL; + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return r; + } + + return 1; +} + +int subject_read(Subject *s, sd_bus_message *m) { + int r; + + assert(s); + assert(m); + + r = sd_bus_message_enter_container(m, 'a', "{sv}"); + if (r <= 0) + return r; + + while ((r = sd_bus_message_enter_container(m, 'e', "sv")) > 0) { + const char *key; + + r = sd_bus_message_read(m, "s", &key); + if (r < 0) + return r; + + r = subject_read_attribute(s, key, m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 1; +} + +int subject_append(Subject *s, sd_bus_message *m) { + int r; + + assert(s); + assert(m); + + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + return r; + + if (s->pid > 0 && s->pid <= INT32_MAX) { + r = sd_bus_message_append(m, "e", "sv", "pid", "i", s->pid); + if (r < 0) + return r; + } + + if (s->tid > 0 && s->tid <= INT32_MAX) { + r = sd_bus_message_append(m, "e", "sv", "tid", "i", s->tid); + if (r < 0) + return r; + } + + if (s->euid != UID_INVALID && s->euid <= UINT32_MAX) { + r = sd_bus_message_append(m, "e", "sv", "euid", "u", s->euid); + if (r < 0) + return r; + } + + if (s->fsuid != UID_INVALID && s->fsuid <= UINT32_MAX) { + r = sd_bus_message_append(m, "e", "sv", "fsuid", "u", s->fsuid); + if (r < 0) + return r; + } + + if (s->egid != GID_INVALID && s->egid <= UINT32_MAX) { + r = sd_bus_message_append(m, "e", "sv", "egid", "u", s->egid); + if (r < 0) + return r; + } + + if (s->fsgid != GID_INVALID && s->fsgid <= UINT32_MAX) { + r = sd_bus_message_append(m, "e", "sv", "fsgid", "u", s->fsgid); + if (r < 0) + return r; + } + + if (s->supplementary_gids) { + size_t i; + + r = sd_bus_message_open_container(m, 'e', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "supplementary-gids"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "au"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "u"); + if (r < 0) + return r; + + for (i = 0; i < s->n_supplementary_gids; ++i) { + gid_t g = s->supplementary_gids[i]; + + if (g != GID_INVALID && g <= UINT32_MAX) { + r = sd_bus_message_append(m, "u", g); + if (r < 0) + return r; + } + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + if (s->session_id) { + r = sd_bus_message_append(m, "e", "sv", "session-id", "s", s->session_id); + if (r < 0) + return r; + } + + if (s->unique_name) { + r = sd_bus_message_append(m, "e", "sv", "unique-name", "s", s->unique_name); + if (r < 0) + return r; + } + + if (s->well_known_names) { + r = sd_bus_message_open_container(m, 'e', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "well-known-names"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, s->well_known_names); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(m); +} + +bool subject_is_superset(Subject *s, Subject *subset) { + size_t i, j; + + assert(s); + assert(subset); + + if (s->pid > 0 && s->pid != subset->pid) + return false; + + if (s->tid > 0 && s->tid != subset->tid) + return false; + + if (s->euid != UID_INVALID && s->euid != subset->euid) + return false; + + if (s->fsuid != UID_INVALID && s->fsuid != subset->fsuid) + return false; + + if (s->egid != GID_INVALID && s->egid != subset->egid) + return false; + + if (s->fsgid != GID_INVALID && s->fsgid != subset->fsgid) + return false; + + if (s->supplementary_gids) { + if (!subset->supplementary_gids) + return false; + + for (i = 0; i < subset->n_supplementary_gids; ++i) { + for (j = 0; j < s->n_supplementary_gids; ++j) + if (s->supplementary_gids[j] == subset->supplementary_gids[i]) + break; + + if (j >= s->n_supplementary_gids) + return false; + } + } + + if (s->session_id && !streq_ptr(s->session_id, subset->session_id)) + return false; + + if (s->unique_name && !streq_ptr(s->unique_name, subset->unique_name)) + return false; + + if (s->well_known_names) { + if (!subset->well_known_names) + return false; + + for (i = 0; subset->well_known_names[i]; ++i) { + for (j = 0; s->well_known_names[j]; ++j) + if (streq(s->well_known_names[j], subset->well_known_names[i])) + break; + + if (!s->well_known_names[j]) + return false; + } + } + + return true; +} diff --git a/src/authority/authorityd.c b/src/authority/authorityd.c new file mode 100644 index 000000000..17cba2570 --- /dev/null +++ b/src/authority/authorityd.c @@ -0,0 +1,67 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include "authorityd.h" +#include "log.h" +#include "sd-daemon.h" +#include "util.h" + +int main(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto out; + } + + r = manager_new(&m, false); + if (r < 0) { + log_error_errno(r, "Could not create manager: %m"); + goto out; + } + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = manager_run(m); + if (r < 0) { + log_error_errno(r, "Cannot run manager: %m"); + goto out; + } + +out: + sd_notify(false, + "STATUS=Shutting down..."); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/authority/authorityd.h b/src/authority/authorityd.h new file mode 100644 index 000000000..686ee57c5 --- /dev/null +++ b/src/authority/authorityd.h @@ -0,0 +1,197 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include "hashmap.h" +#include "macro.h" +#include "sd-bus.h" +#include "sd-event.h" +#include "set.h" +#include "util.h" + +typedef struct Action Action; +typedef struct Subject Subject; +typedef struct Agent Agent; +typedef struct Transaction Transaction; +typedef struct Factor Factor; +typedef struct Authorization Authorization; +typedef struct Manager Manager; + +/* action */ + +struct Action { + unsigned long ref; + + char *action_id; +}; + +int action_new(Action **out); +Action *action_ref(Action *action); +Action *action_unref(Action *action); +int action_read(Action *action, sd_bus_message *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Action*, action_unref); + +/* subject */ + +struct Subject { + unsigned long ref; + + pid_t pid; + pid_t tid; + uid_t euid; + uid_t fsuid; + gid_t egid; + gid_t fsgid; + gid_t *supplementary_gids; + size_t n_supplementary_gids; + char *session_id; + char *unique_name; + char **well_known_names; +}; + +int subject_new(Subject **out); +Subject *subject_ref(Subject *subject); +Subject *subject_unref(Subject *subject); +int subject_read(Subject *subject, sd_bus_message *m); +int subject_append(Subject *subject, sd_bus_message *m); +bool subject_is_superset(Subject *subject, Subject *subset); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Subject*, subject_unref); + +/* agent */ + +struct Agent { + Manager *manager; + char id[DECIMAL_STR_MAX(uint64_t)]; + + sd_bus_track *owner_track; + char *owner_busid; + + Set *subjects; +}; + +int agent_new(Agent **out, Manager *m, const char *sender); +Agent *agent_free(Agent *agent); +int agent_read(Agent *agent, sd_bus_message *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Agent*, agent_free); + +/* transaction */ + +struct Transaction { + Authorization *authorization; + Factor *factor; +}; + +int transaction_new(Transaction **out, Authorization *authorization, Factor *factor); +Transaction *transaction_free(Transaction *transaction); + +/* factor */ + +struct Factor { + Manager *manager; + char id[DECIMAL_STR_MAX(uint64_t)]; + + Hashmap *transaction_map; + + bool interactive : 1; + bool non_interactive : 1; +}; + +int factor_new(Factor **out, Manager *m); +Factor *factor_free(Factor *factor); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Factor*, factor_free); + +/* authorization */ + +enum { + AUTHORIZATION_RUN_TEMPORARY, + AUTHORIZATION_RUN_RULES, + AUTHORIZATION_RUN_NON_INTERACTIVE, + AUTHORIZATION_RUN_INTERACTIVE, +}; + +struct Authorization { + Manager *manager; + char id[DECIMAL_STR_MAX(uint64_t)]; + + Hashmap *transaction_map; + + uid_t uid; + Subject *subject; + Action *action; + + sd_bus_track *owner_track; + char *owner_busid; + char *owner_unique; + sd_bus_message *owner_msg; + sd_event_source *run_src; + unsigned int run_state; + Agent *agent; + + bool interactive : 1; + bool started : 1; + bool stopped : 1; + bool live : 1; +}; + +int authorization_new(Authorization **out, Manager *m); +Authorization *authorization_free(Authorization *auth); +int authorization_start(Authorization *auth, sd_bus_message *m, const char *block_unique); +void authorization_stop(Authorization *auth, int ret); +Authorization *authorization_lookup(Manager *m, const char *sender, const char *block_unique); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Authorization*, authorization_free); + +/* manager */ + +struct Manager { + sd_event *event; + sd_bus *bus; + char **dirs; + + Hashmap *agent_map; + Hashmap *factor_map; + Hashmap *authorization_map; + uint64_t agent_ids; + uint64_t factor_ids; + uint64_t authorization_ids; + + Hashmap *agent_by_owner; + Hashmap *authorization_by_unique; +}; + +int manager_new(Manager **out, bool system_level); +Manager *manager_free(Manager *m); +int manager_bus_init(Manager *m); +int manager_bus_emit_authorization_new(Manager *m, Authorization *authorization); +int manager_bus_emit_authorization_removed(Manager *m, Authorization *authorization); +int manager_run(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); diff --git a/src/shared/authority-agent.c b/src/shared/authority-agent.c new file mode 100644 index 000000000..80fa2fb48 --- /dev/null +++ b/src/shared/authority-agent.c @@ -0,0 +1,954 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include "authority-agent.h" +#include "bus-error.h" +#include "bus-util.h" +#include "hashmap.h" +#include "macro.h" +#include "sd-bus.h" +#include "util.h" + +typedef struct AuthorityPrompt AuthorityPrompt; +typedef struct AuthorityTransaction AuthorityTransaction; +typedef struct AuthorityFactor AuthorityFactor; +typedef struct AuthorityAuthorization AuthorityAuthorization; + +struct AuthorityPrompt { + char prompt[LINE_MAX]; + char input[LINE_MAX]; + size_t length; + bool hidden : 1; + bool valid : 1; +}; + +struct AuthorityTransaction { + AuthorityAuthorization *authorization; + AuthorityFactor *factor; + + AuthorityPrompt prompt; + + bool stray : 1; + bool shown : 1; +}; + +struct AuthorityFactor { + AuthorityAgent *agent; + char *path; + + Hashmap *transaction_map; + + char *name; +}; + +struct AuthorityAuthorization { + AuthorityAgent *agent; + char *path; + + OrderedHashmap *transaction_map; + AuthorityTransaction *active; +}; + +struct AuthorityAgent { + int ttyin; + int ttyout; + sd_bus *bus; + sd_event_source *src_tty; + sd_bus_slot *slot_interfaces_added; + sd_bus_slot *slot_interfaces_removed; + sd_bus_slot *slot_properties_changed; + sd_bus_slot *slot_prompt; + sd_bus_slot *slot_get_managed_objects; + sd_bus_slot *slot_new_agent; + + OrderedHashmap *authorization_map; + Hashmap *factor_map; + AuthorityAuthorization *active; + AuthorityPrompt *prompt; + char *agent_path; + + bool allow_draft : 1; +}; + +static bool authority_transaction_is_active(AuthorityTransaction *transaction); +static void authority_transaction_show(AuthorityTransaction *transaction); +static void authority_transaction_hide(AuthorityTransaction *transaction); +static bool authority_authorization_is_active(AuthorityAuthorization *authorization); +static void authority_agent_draft(AuthorityAgent *agent); + +static bool ui_has_prompt(AuthorityAgent *agent) { + return agent->prompt; +} + +static void ui_show_prompt(AuthorityAgent *agent) { + static const char asterisk[] = "****************"; + + if (!ui_has_prompt(agent)) + return; + + loop_write(agent->ttyout, agent->prompt->prompt, strlen(agent->prompt->prompt), true); + + if (agent->prompt->hidden) { + size_t i, t; + + for (i = 0; i < agent->prompt->length; i += t) { + t = MIN(agent->prompt->length - i, sizeof(asterisk - 1)); + loop_write(agent->ttyout, asterisk, t, true); + } + } else { + loop_write(agent->ttyout, agent->prompt->input, agent->prompt->length, true); + } +} + +static void ui_hide_prompt(AuthorityAgent *agent) { + /* erase line and carriage return */ + if (ui_has_prompt(agent)) + loop_write(agent->ttyout, "\e[2K\r", 5, true); +} + +static void ui_print(AuthorityAgent *agent, const char *format, ...) { + char message[LINE_MAX + 1]; + va_list list; + int len; + + ui_hide_prompt(agent); + + va_start(list, format); + len = vsnprintf(message, LINE_MAX, format, list); + va_end(list); + + if (len >= LINE_MAX) + len = LINE_MAX - 1; + + if (len == 0 || message[len - 1] != '\n') { + message[len++] = '\n'; + message[len] = 0; + } + + loop_write(agent->ttyout, message, len, true); + + ui_show_prompt(agent); +} + +static bool authority_transaction_is_active(AuthorityTransaction *transaction) { + return transaction && transaction->authorization->active == transaction; +} + +static AuthorityTransaction *authority_transaction_free(AuthorityTransaction *transaction) { + if (!transaction) + return NULL; + + hashmap_remove_value(transaction->factor->transaction_map, transaction->authorization->path, transaction); + ordered_hashmap_remove_value(transaction->authorization->transaction_map, transaction->factor->path, transaction); + + /* if active, deselect ourself and draft next */ + if (authority_transaction_is_active(transaction)) { + if (authority_authorization_is_active(transaction->authorization)) + authority_transaction_hide(transaction); + transaction->authorization->active = NULL; + authority_agent_draft(transaction->authorization->agent); + } + + assert(!transaction->shown); + + free(transaction); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(AuthorityTransaction*, authority_transaction_free); + +static int authority_transaction_new(AuthorityTransaction **out, AuthorityAuthorization *authorization, AuthorityFactor *factor) { + _cleanup_(authority_transaction_freep) AuthorityTransaction *transaction = NULL; + int r; + + assert(out); + assert(authorization); + assert(factor); + + transaction = new0(AuthorityTransaction, 1); + if (!transaction) + return -ENOMEM; + + transaction->authorization = authorization; + transaction->factor = factor; + + r = ordered_hashmap_ensure_allocated(&authorization->transaction_map, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&factor->transaction_map, &string_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(authorization->transaction_map, factor->path, transaction); + if (r < 0) + return r; + + r = hashmap_put(factor->transaction_map, authorization->path, transaction); + if (r < 0) + return r; + + *out = transaction; + transaction = NULL; + return 0; +} + +static void authority_transaction_show(AuthorityTransaction *transaction) { + assert(transaction); + assert(authority_transaction_is_active(transaction)); + assert(authority_authorization_is_active(transaction->authorization)); + assert(!transaction->shown); + + transaction->shown = true; + + if (transaction->prompt.valid) { + AuthorityAgent *agent = transaction->authorization->agent; + + agent->prompt = &transaction->prompt; + ui_show_prompt(agent); + } +} + +static void authority_transaction_hide(AuthorityTransaction *transaction) { + assert(transaction); + assert(authority_transaction_is_active(transaction)); + assert(authority_authorization_is_active(transaction->authorization)); + assert(transaction->shown); + + transaction->shown = false; + + if (transaction->prompt.valid) { + AuthorityAgent *agent = transaction->authorization->agent; + + ui_hide_prompt(agent); + agent->prompt = NULL; + } +} + +static void authority_transaction_prompt(AuthorityTransaction *transaction, sd_bus_message *m) { + AuthorityPrompt *prompt; + AuthorityAgent *agent; + + assert(transaction); + + agent = transaction->authorization->agent; + prompt = &transaction->prompt; + prompt->valid = true; + prompt->hidden = false; + prompt->input[0] = 0; + strcpy(prompt->prompt, "Prompt: "); + + if (transaction->shown) { + ui_hide_prompt(agent); + agent->prompt = &transaction->prompt; + ui_show_prompt(agent); + } else { + /* we gained a prompt and thus might get drafted */ + authority_agent_draft(transaction->authorization->agent); + } +} + +static void authority_transaction_feed(AuthorityTransaction *transaction, char c) { + AuthorityAgent *agent; + + assert(transaction); + + agent = transaction->authorization->agent; + + switch (c) { + case '\n': + if (!transaction->prompt.valid) + break; + + if (transaction->shown) + ui_hide_prompt(agent); + + agent->prompt = NULL; + transaction->prompt.valid = false; + + break; + default: + if (!transaction->prompt.valid) + break; + + break; + } +} + +static AuthorityFactor *authority_factor_free(AuthorityFactor *factor) { + AuthorityTransaction *transaction; + + if (!factor) + return NULL; + + hashmap_remove_value(factor->agent->factor_map, factor->path, factor); + + while ((transaction = hashmap_first(factor->transaction_map))) + authority_transaction_free(transaction); + + hashmap_free(factor->transaction_map); + free(factor->name); + free(factor->path); + free(factor); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(AuthorityFactor*, authority_factor_free); + +static int authority_factor_new(AuthorityFactor **out, AuthorityAgent *agent, const char *path) { + _cleanup_(authority_factor_freep) AuthorityFactor *factor = NULL; + int r; + + assert(out); + assert(agent); + assert(path); + + factor = new0(AuthorityFactor, 1); + if (!factor) + return -ENOMEM; + + factor->agent = agent; + + factor->path = strdup(path); + if (!factor->path) + return -ENOMEM; + + r = hashmap_ensure_allocated(&agent->factor_map, &string_hash_ops); + if (r < 0) + return r; + + r = hashmap_put(agent->factor_map, factor->path, factor); + if (r < 0) + return r; + + *out = factor; + factor = NULL; + return 0; +} + +static int authority_factor_properties_changed(AuthorityFactor *factor, sd_bus_message *m) { + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(AuthorityFactor, name) }, + { }, + }; + + return bus_message_map_all_properties(factor->agent->bus, m, map, factor); +} + +static bool authority_authorization_is_active(AuthorityAuthorization *authorization) { + return authorization && authorization->agent->active == authorization; +} + +static AuthorityAuthorization *authority_authorization_free(AuthorityAuthorization *authorization) { + AuthorityTransaction *transaction; + + if (!authorization) + return NULL; + + ordered_hashmap_remove_value(authorization->agent->authorization_map, authorization->path, authorization); + + /* hide and deselect active transaction */ + if (authority_authorization_is_active(authorization) && authorization->active) + authority_transaction_hide(authorization->active); + authorization->active = NULL; + + /* if active, deselect authorization and draft */ + if (authority_authorization_is_active(authorization)) { + authorization->agent->active = NULL; + authority_agent_draft(authorization->agent); + } + + while ((transaction = ordered_hashmap_first(authorization->transaction_map))) + authority_transaction_free(transaction); + + ordered_hashmap_free(authorization->transaction_map); + free(authorization->path); + free(authorization); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(AuthorityAuthorization*, authority_authorization_free); + +static int authority_authorization_new(AuthorityAuthorization **out, AuthorityAgent *agent, const char *path) { + _cleanup_(authority_authorization_freep) AuthorityAuthorization *authorization = NULL; + int r; + + assert(out); + assert(agent); + assert(path); + + authorization = new0(AuthorityAuthorization, 1); + if (!authorization) + return -ENOMEM; + + authorization->agent = agent; + + authorization->path = strdup(path); + if (!authorization->path) + return -ENOMEM; + + r = ordered_hashmap_ensure_allocated(&agent->authorization_map, &string_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_put(agent->authorization_map, authorization->path, authorization); + if (r < 0) + return r; + + *out = authorization; + authorization = NULL; + return 0; +} + +static int authority_authorization_get_factors_fn(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + AuthorityAuthorization *authorization = userdata; + AuthorityTransaction *transaction; + const char *object; + Iterator iter; + int r; + + ORDERED_HASHMAP_FOREACH(transaction, authorization->transaction_map, iter) + transaction->stray = true; + + r = sd_bus_message_enter_container(m, 'a', "o"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(m, "o", &object)) > 0) { + AuthorityFactor *factor; + + factor = hashmap_get(authorization->agent->factor_map, object); + if (!factor) { + r = authority_factor_new(&factor, authorization->agent, object); + if (r < 0) + return r; + } + + transaction = hashmap_get(factor->transaction_map, authorization->path); + if (transaction) { + transaction->stray = false; + } else { + r = authority_transaction_new(&transaction, authorization, factor); + if (r < 0) + return r; + } + } + if (r < 0) + return r; + + /* disable drafting temporarily to 'batch-remove' old transactions */ + authorization->agent->allow_draft = false; + + ORDERED_HASHMAP_FOREACH(transaction, authorization->transaction_map, iter) + if (transaction->stray) + authority_transaction_free(transaction); + + authorization->agent->allow_draft = true; + authority_agent_draft(authorization->agent); + + return 0; +} + +static int authority_authorization_properties_changed(AuthorityAuthorization *authorization, sd_bus_message *m) { + static const struct bus_properties_map map[] = { + { "Factors", "ao", authority_authorization_get_factors_fn, 0 }, + { }, + }; + + return bus_message_map_all_properties(authorization->agent->bus, m, map, authorization); +} + +static void authority_authorization_next(AuthorityAuthorization *authorization) { + AuthorityTransaction *next = NULL; + + assert(authorization); + + if (authorization->active) + next = ordered_hashmap_next(authorization->transaction_map, authorization->active->factor->path); + if (!next) + next = ordered_hashmap_first(authorization->transaction_map); + + /* TODO: only select transactions with prompts */ + + /* bail out if nothing to do */ + if (!next || next == authorization->active) + return; + + if (authority_authorization_is_active(authorization) && authorization->active) + authority_transaction_hide(authorization->active); + + authorization->active = next; + + if (authority_authorization_is_active(authorization)) + authority_transaction_show(authorization->active); +} + +static void authority_agent_draft(AuthorityAgent *agent) { + assert(agent); + + if (!agent->allow_draft) + return; + + if (!agent->active) + agent->active = ordered_hashmap_first(agent->authorization_map); + + if (agent->active) { + if (!agent->active->active) + agent->active->active = ordered_hashmap_first(agent->active->transaction_map); + if (agent->active->active && !agent->active->active->shown) + authority_transaction_show(agent->active->active); + } +} + +static void authority_agent_feed(AuthorityAgent *agent, char *input, size_t len) { + size_t i; + + assert(agent); + + for (i = 0; i < len; ++i) { + switch (input[i]) { + case '\t': + if (agent->active) + authority_authorization_next(agent->active); + break; + default: + if (agent->active && agent->active->active) + authority_transaction_feed(agent->active->active, input[i]); + break; + } + } +} + +static int authority_agent_tty_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + AuthorityAgent *agent = userdata; + int r = 0; + + if (revents & EPOLLIN) { + char buf[LINE_MAX]; + ssize_t l; + + l = read(fd, buf, sizeof(buf)); + if (l < 0) { + if (errno != EAGAIN) + r = -errno; + } else { + authority_agent_feed(agent, buf, l); + return 0; + } + } + + if (r < 0 || revents & (EPOLLHUP | EPOLLERR)) { + sd_event_source_set_enabled(source, SD_EVENT_OFF); + errno = r < 0 ? -r : EPIPE; + ui_print(agent, "HUP/ERR on TTY input: %m"); + } + + return 0; +} + +static int authority_agent_properties_changed(AuthorityAgent *agent, const char *object, sd_bus_message *m) { + const char *interface; + int r; + + assert(agent); + assert(object); + assert(m); + + r = sd_bus_message_read(m, "s", &interface); + if (r < 0) + return r; + + if (streq(interface, "org.freedesktop.authority1.Authorization")) { + AuthorityAuthorization *authorization; + + authorization = ordered_hashmap_get(agent->authorization_map, object); + if (!authorization) { + r = authority_authorization_new(&authorization, agent, object); + if (r < 0) + return r; + } + + r = authority_authorization_properties_changed(authorization, m); + if (r < 0) + return r; + } else if (streq(interface, "org.freedesktop.authority1.Factor")) { + AuthorityFactor *factor; + + factor = hashmap_get(agent->factor_map, object); + if (!factor) { + r = authority_factor_new(&factor, agent, object); + if (r < 0) + return r; + } + + r = authority_factor_properties_changed(factor, m); + if (r < 0) + return r; + } else { + r = sd_bus_message_skip(m, "a{sv}"); + if (r < 0) + return r; + } + + return 0; +} + +static int authority_agent_interfaces_added_fn(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + AuthorityAgent *agent = userdata; + const char *object; + int r; + + r = sd_bus_message_read(m, "o", &object); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(m, 'e', "sa{sv}")) > 0) { + r = authority_agent_properties_changed(agent, object, m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 0; +} + +static int authority_agent_interfaces_removed_fn(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + AuthorityAgent *agent = userdata; + const char *object, *interface; + int r; + + r = sd_bus_message_read(m, "s", &object); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, 'a', "s"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(m, "s", &interface)) > 0) { + if (streq(interface, "org.freedesktop.authority1.Authorization")) + authority_authorization_free(ordered_hashmap_get(agent->authorization_map, object)); + else if (streq(interface, "org.freedesktop.authority1.Factor")) + authority_factor_free(hashmap_get(agent->factor_map, object)); + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 0; +} + +static int authority_agent_properties_changed_fn(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + AuthorityAgent *agent = userdata; + const char *object; + + object = sd_bus_message_get_path(m); + if (!object) + return 0; + + return authority_agent_properties_changed(agent, object, m); +} + +static int authority_agent_prompt_fn(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *error) { + AuthorityAuthorization *authorization; + AuthorityTransaction *transaction; + AuthorityAgent *agent = userdata; + const char *object; + int r; + + object = sd_bus_message_get_path(m); + if (!object) + return 0; + + authorization = ordered_hashmap_get(agent->authorization_map, object); + if (!authorization) + return 0; + + r = sd_bus_message_read(m, "o", &object); + if (r < 0) + return r; + + transaction = ordered_hashmap_get(authorization->transaction_map, object); + if (transaction) + authority_transaction_prompt(transaction, m); + + return 0; +} + +static int authority_agent_get_managed_objects_fn(sd_bus *bus, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { + const sd_bus_error *error = NULL; + AuthorityAgent *agent = userdata; + int r; + + agent->slot_get_managed_objects = sd_bus_slot_unref(agent->slot_get_managed_objects); + + if (sd_bus_message_is_method_error(reply, NULL)) { + error = sd_bus_message_get_error(reply); + return -sd_bus_error_get_errno(error); + } + + r = sd_bus_message_enter_container(reply, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(reply, 'e', "oa{sa{sv}}")) > 0) { + r = authority_agent_interfaces_added_fn(bus, reply, userdata, ret_error); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + return 0; +} + +static int authority_agent_new_agent_fn(sd_bus *bus, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { + const sd_bus_error *error = NULL; + AuthorityAgent *agent = userdata; + const char *path; + int r; + + agent->slot_new_agent = sd_bus_slot_unref(agent->slot_new_agent); + + if (sd_bus_message_is_method_error(reply, NULL)) { + error = sd_bus_message_get_error(reply); + r = -sd_bus_error_get_errno(error); + goto error; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + goto error; + + agent->agent_path = strdup(path); + if (!agent->agent_path) { + r = -ENOMEM; + goto error; + } + + agent->allow_draft = true; + authority_agent_draft(agent); + return 0; + +error: + ui_print(agent, "Cannot register agent: %s", bus_error_message(error, r)); + return r; +} + +int authority_agent_new(AuthorityAgent **out, int ttyin, int ttyout, sd_bus *bus) { + _cleanup_(authority_agent_freep) AuthorityAgent *agent = NULL; + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + sd_event *event; + int r; + + assert(out); + assert(ttyin >= 0); + assert(ttyout >= 0); + assert(bus); + + if (!isatty(ttyin) || !isatty(ttyout)) + return -ENOTTY; + + event = sd_bus_get_event(bus); + if (!event) + return -EINVAL; + + agent = new0(AuthorityAgent, 1); + if (!agent) + return -ENOMEM; + + agent->ttyin = ttyin; + agent->ttyout = ttyout; + agent->bus = sd_bus_ref(bus); + + r = sd_event_add_io(event, &agent->src_tty, ttyin, EPOLLIN, authority_agent_tty_fn, agent); + if (r < 0) + return r; + + r = sd_bus_add_match(agent->bus, + &agent->slot_interfaces_added, + "type='signal'," + "sender='org.freedesktop.authority1'," + "interface='org.freedesktop.DBus.ObjectManager'," + "member='InterfacesAdded'", + authority_agent_interfaces_added_fn, + agent); + if (r < 0) + return r; + + r = sd_bus_add_match(agent->bus, + &agent->slot_interfaces_removed, + "type='signal'," + "sender='org.freedesktop.authority1'," + "interface='org.freedesktop.DBus.ObjectManager'," + "member='InterfacesRemoved'", + authority_agent_interfaces_removed_fn, + agent); + if (r < 0) + return r; + + r = sd_bus_add_match(agent->bus, + &agent->slot_properties_changed, + "type='signal'," + "sender='org.freedesktop.authority1'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'," + "arg1='org.freedesktop.authority1.Authorization'", + authority_agent_properties_changed_fn, + agent); + if (r < 0) + return r; + + r = sd_bus_add_match(agent->bus, + &agent->slot_prompt, + "type='signal'," + "sender='org.freedesktop.authority1'," + "interface='org.freedesktop.authority1.Authorization'," + "member='Prompt'", + authority_agent_prompt_fn, + agent); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call(agent->bus, + &m, + "org.freedesktop.authority1", + "/org/freedesktop/authority1", + "org.freedesktop.DBus.ObjectManager", + "GetManagedObjects"); + if (r < 0) + return r; + + r = sd_bus_call_async(agent->bus, &agent->slot_get_managed_objects, m, authority_agent_get_managed_objects_fn, agent, 0); + if (r < 0) + return r; + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_method_call(agent->bus, + &m, + "org.freedesktop.authority1", + "/org/freedesktop/authority1", + "org.freedesktop.authority1.Manager", + "NewAgent"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "sas", "<WIP>", 2, "self", "queue"); + if (r < 0) + return r; + + r = sd_bus_call_async(agent->bus, &agent->slot_new_agent, m, authority_agent_new_agent_fn, agent, 0); + if (r < 0) + return r; + + *out = agent; + agent = NULL; + return 0; +} + +AuthorityAgent *authority_agent_free(AuthorityAgent *agent) { + AuthorityAuthorization *authorization; + AuthorityFactor *factor; + int r; + + if (!agent) + return NULL; + + /* If we have a registered agent or NewAgent is pending, remove it */ + if (agent->agent_path || agent->slot_new_agent) { + _cleanup_bus_message_unref_ sd_bus_message *m = NULL; + + r = sd_bus_message_new_method_call(agent->bus, &m, + "org.freedesktop.authority1", + "/org/freedesktop/authority1", + "org.freedesktop.authority1.Manager", + "RemoveAgent"); + if (r >= 0) { + r = sd_bus_message_append(m, "o", "/org/freedesktop/authority1/agent/self"); + if (r >= 0) + sd_bus_send(agent->bus, m, NULL); + } + + if (r < 0) + ui_print(agent, "Cannot unregister agent: %s", bus_error_message(NULL, r)); + } + + /* disable drafting for silent shutdown */ + agent->allow_draft = false; + + /* drop all factors and authorizations */ + while ((factor = hashmap_first(agent->factor_map))) + authority_factor_free(factor); + while ((authorization = ordered_hashmap_first(agent->authorization_map))) + authority_authorization_free(authorization); + + assert(!agent->active); + assert(!agent->prompt); + assert(ordered_hashmap_isempty(agent->authorization_map)); + assert(hashmap_isempty(agent->factor_map)); + + free(agent->agent_path); + hashmap_free(agent->factor_map); + ordered_hashmap_free(agent->authorization_map); + agent->slot_new_agent = sd_bus_slot_unref(agent->slot_new_agent); + agent->slot_get_managed_objects = sd_bus_slot_unref(agent->slot_get_managed_objects); + agent->slot_prompt = sd_bus_slot_unref(agent->slot_prompt); + agent->slot_properties_changed = sd_bus_slot_unref(agent->slot_properties_changed); + agent->slot_interfaces_removed = sd_bus_slot_unref(agent->slot_interfaces_removed); + agent->slot_interfaces_added = sd_bus_slot_unref(agent->slot_interfaces_added); + agent->src_tty = sd_event_source_unref(agent->src_tty); + agent->bus = sd_bus_unref(agent->bus); + free(agent); + + return NULL; +} diff --git a/src/shared/authority-agent.h b/src/shared/authority-agent.h new file mode 100644 index 000000000..723c514b4 --- /dev/null +++ b/src/shared/authority-agent.h @@ -0,0 +1,34 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014-2015 David Herrmann <dh.herrmann@gmail.com> + + 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 "macro.h" +#include "sd-bus.h" +#include "util.h" + +typedef struct AuthorityAgent AuthorityAgent; + +int authority_agent_new(AuthorityAgent **out, int ttyin, int ttyout, sd_bus *bus); +AuthorityAgent *authority_agent_free(AuthorityAgent *agent); + +DEFINE_TRIVIAL_CLEANUP_FUNC(AuthorityAgent*, authority_agent_free); |