diff --git a/src/libsystemd/sd-json/json-util.c b/src/libsystemd/sd-json/json-util.c index db7a2ac1e8..67e50e6c51 100644 --- a/src/libsystemd/sd-json/json-util.c +++ b/src/libsystemd/sd-json/json-util.c @@ -2,6 +2,7 @@ #include "alloc-util.h" #include "devnum-util.h" +#include "env-util.h" #include "fd-util.h" #include "glyph-util.h" #include "in-addr-util.h" @@ -369,6 +370,40 @@ int json_dispatch_devnum(const char *name, sd_json_variant *variant, sd_json_dis return 0; } +int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + _cleanup_strv_free_ char **n = NULL; + char ***l = userdata; + int r; + + if (sd_json_variant_is_null(variant)) { + *l = strv_free(*l); + return 0; + } + + if (!sd_json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + for (size_t i = 0; i < sd_json_variant_elements(variant); i++) { + sd_json_variant *e; + const char *a; + + e = sd_json_variant_by_index(variant, i); + if (!sd_json_variant_is_string(e)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name)); + + assert_se(a = sd_json_variant_string(e)); + + if (!env_assignment_is_valid(a)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of environment variables.", strna(name)); + + r = strv_env_replace_strdup(&n, a); + if (r < 0) + return json_log_oom(variant, flags); + } + + return strv_free_and_replace(*l, n); +} + static int json_variant_new_stat(sd_json_variant **ret, const struct stat *st) { char mode[STRLEN("0755")+1]; diff --git a/src/libsystemd/sd-json/json-util.h b/src/libsystemd/sd-json/json-util.h index b824fa8a81..808df64d98 100644 --- a/src/libsystemd/sd-json/json-util.h +++ b/src/libsystemd/sd-json/json-util.h @@ -116,6 +116,7 @@ int json_dispatch_path(const char *name, sd_json_variant *variant, sd_json_dispa int json_dispatch_pidref(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_devnum(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); int json_dispatch_ifindex(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); +int json_dispatch_strv_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata); static inline int json_variant_unbase64_iovec(sd_json_variant *v, struct iovec *ret) { return sd_json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index de70c0a188..2ed779d117 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -294,59 +294,11 @@ int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_send(NULL, reply, NULL); } -static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { - int r; - - assert(m); - assert(ret); - - switch (m->class) { - - case MACHINE_HOST: - *ret = NULL; - break; - - case MACHINE_CONTAINER: { - _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; - char *address; - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - if (asprintf(&address, "x-machine-unix:pid=%" PID_PRI, m->leader.pid) < 0) - return -ENOMEM; - - bus->address = address; - bus->bus_client = true; - bus->trusted = false; - bus->runtime_scope = RUNTIME_SCOPE_SYSTEM; - - r = sd_bus_start(bus); - if (r == -ENOENT) - return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); - if (r < 0) - return r; - - *ret = TAKE_PTR(bus); - break; - } - - default: - return -EOPNOTSUPP; - } - - return 0; -} - int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; _cleanup_close_ int master = -EBADF; - sd_bus *container_bus = NULL; Machine *m = ASSERT_PTR(userdata); - const char *p, *getty; int r; assert(message); @@ -372,18 +324,7 @@ int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bu if (master < 0) return master; - p = path_startswith(pty_name, "/dev/pts/"); - assert(p); - - r = container_bus_new(m, error, &allocated_bus); - if (r < 0) - return r; - - container_bus = allocated_bus ?: m->manager->bus; - - getty = strjoina("container-getty@", p, ".service"); - - r = bus_call_method(container_bus, bus_systemd_mgr, "StartUnit", error, NULL, "ss", getty, "replace"); + r = machine_start_getty(m, pty_name, error); if (r < 0) return r; @@ -399,15 +340,13 @@ int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bu } int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; - sd_bus *container_bus = NULL; - _cleanup_close_ int master = -EBADF, slave = -EBADF; + _cleanup_close_ int master = -EBADF; _cleanup_strv_free_ char **env = NULL, **args_wire = NULL, **args = NULL; _cleanup_free_ char *command_line = NULL; Machine *m = ASSERT_PTR(userdata); - const char *unit, *user, *path, *description, *utmp_id; + const char *user, *path; int r; assert(message); @@ -420,25 +359,10 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu if (r < 0) return r; if (isempty(path)) { - path = "/bin/sh"; - - args = new0(char*, 3 + 1); + path = machine_default_shell_path(); + args = machine_default_shell_args(user); if (!args) return -ENOMEM; - args[0] = strdup("sh"); - if (!args[0]) - return -ENOMEM; - args[1] = strdup("-c"); - if (!args[1]) - return -ENOMEM; - r = asprintf(&args[2], - "shell=$(getent passwd %s 2>/dev/null | { IFS=: read _ _ _ _ _ _ x; echo \"$x\"; })\n"\ - "exec \"${shell:-/bin/sh}\" -l", /* -l is means --login */ - user); - if (r < 0) { - args[2] = NULL; - return -ENOMEM; - } } else { if (!path_is_absolute(path)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path); @@ -484,153 +408,10 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu if (master < 0) return master; - /* First try to get an fd for the PTY peer via the new racefree ioctl(), directly. Otherwise go via - * joining the namespace, because it goes by path */ - slave = pty_open_peer_racefree(master, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (ERRNO_IS_NEG_NOT_SUPPORTED(slave)) - slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (slave < 0) - return slave; - - utmp_id = path_startswith(pty_name, "/dev/"); - assert(utmp_id); - - r = container_bus_new(m, error, &allocated_bus); + r = machine_start_shell(m, master, pty_name, user, path, args, env, error); if (r < 0) return r; - container_bus = allocated_bus ?: m->manager->bus; - - r = bus_message_new_method_call(container_bus, &tm, bus_systemd_mgr, "StartTransientUnit"); - if (r < 0) - return r; - - /* Name and mode */ - const char *p = ASSERT_PTR(path_startswith(pty_name, "/dev/pts/")); - - unit = strjoina("container-shell@", p, ".service"); - r = sd_bus_message_append(tm, "ss", unit, "fail"); - if (r < 0) - return r; - - /* Properties */ - r = sd_bus_message_open_container(tm, 'a', "(sv)"); - if (r < 0) - return r; - - description = strjoina("Shell for User ", user); - r = sd_bus_message_append(tm, - "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", - "Description", "s", description, - "StandardInputFileDescriptor", "h", slave, - "StandardOutputFileDescriptor", "h", slave, - "StandardErrorFileDescriptor", "h", slave, - "SendSIGHUP", "b", true, - "IgnoreSIGPIPE", "b", false, - "KillMode", "s", "mixed", - "TTYPath", "s", pty_name, - "TTYReset", "b", true, - "UtmpIdentifier", "s", utmp_id, - "UtmpMode", "s", "user", - "PAMName", "s", "login", - "WorkingDirectory", "s", "-~"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "(sv)", "User", "s", user); - if (r < 0) - return r; - - if (!strv_isempty(env)) { - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "Environment"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "as"); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, env); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - } - - /* Exec container */ - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "ExecStart"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'a', "(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'r', "sasb"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, args); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "b", true); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - /* Auxiliary units */ - r = sd_bus_message_append(tm, "a(sa(sv))", 0); - if (r < 0) - return r; - - r = sd_bus_call(container_bus, tm, 0, error, NULL); - if (r < 0) - return r; - - slave = safe_close(slave); - r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index bfa4095a3b..8ad3c87469 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -7,6 +7,7 @@ #include "sd-varlink.h" #include "bus-polkit.h" +#include "fd-util.h" #include "hostname-util.h" #include "json-util.h" #include "machine-varlink.h" @@ -16,7 +17,9 @@ #include "process-util.h" #include "signal-util.h" #include "socket-util.h" +#include "string-table.h" #include "string-util.h" +#include "user-util.h" #include "varlink-util.h" static JSON_DISPATCH_ENUM_DEFINE(dispatch_machine_class, MachineClass, machine_class_from_string); @@ -375,3 +378,195 @@ int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met return sd_varlink_reply(link, NULL); } + +typedef enum MachineOpenMode { + MACHINE_OPEN_MODE_TTY, + MACHINE_OPEN_MODE_LOGIN, + MACHINE_OPEN_MODE_SHELL, + _MACHINE_OPEN_MODE_MAX, + _MACHINE_OPEN_MODE_INVALID = -EINVAL, +} MachineOpenMode; + +static const char* const machine_open_mode_table[_MACHINE_OPEN_MODE_MAX] = { + [MACHINE_OPEN_MODE_TTY] = "tty", + [MACHINE_OPEN_MODE_LOGIN] = "login", + [MACHINE_OPEN_MODE_SHELL] = "shell", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(machine_open_mode, MachineOpenMode); +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_machine_open_mode, MachineOpenMode, machine_open_mode_from_string); + +typedef struct MachineOpenParameters { + const char *name, *user; + PidRef pidref; + MachineOpenMode mode; + char *path, **args, **env; +} MachineOpenParameters; + +static void machine_open_paramaters_done(MachineOpenParameters *p) { + assert(p); + pidref_done(&p->pidref); + free(p->path); + strv_free(p->args); + strv_free(p->env); +} + +inline static const char* machine_open_polkit_action(MachineOpenMode mode, MachineClass class) { + switch (mode) { + case MACHINE_OPEN_MODE_TTY: + return class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty"; + case MACHINE_OPEN_MODE_LOGIN: + return class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login"; + case MACHINE_OPEN_MODE_SHELL: + return class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell"; + default: + assert_not_reached(); + } +} + +inline static char** machine_open_polkit_details(MachineOpenMode mode, const char *machine_name, const char *user, const char *path, const char *command_line) { + assert(machine_name); + + switch (mode) { + case MACHINE_OPEN_MODE_TTY: + return strv_new("machine", machine_name); + case MACHINE_OPEN_MODE_LOGIN: + return strv_new("machine", machine_name, "verb", "login"); + case MACHINE_OPEN_MODE_SHELL: + assert(user); + assert(path); + assert(command_line); + return strv_new( + "machine", machine_name, + "verb", "shell", + "user", user, + "program", path, + "command_line", command_line); + default: + assert_not_reached(); + } +} + +int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineOpenParameters), + { "mode", SD_JSON_VARIANT_STRING, json_dispatch_machine_open_mode, offsetof(MachineOpenParameters, mode), SD_JSON_MANDATORY }, + { "user", SD_JSON_VARIANT_STRING, json_dispatch_const_user_group_name, offsetof(MachineOpenParameters, user), SD_JSON_RELAX }, + { "path", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(MachineOpenParameters, path), 0 }, + { "args", SD_JSON_VARIANT_ARRAY, sd_json_dispatch_strv, offsetof(MachineOpenParameters, args), 0 }, + { "environment", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_environment, offsetof(MachineOpenParameters, env), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + _cleanup_close_ int ptmx_fd = -EBADF; + _cleanup_(machine_open_paramaters_done) MachineOpenParameters p = { + .pidref = PIDREF_NULL, + .mode = _MACHINE_OPEN_MODE_INVALID, + }; + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_free_ char *ptmx_name = NULL, *command_line = NULL; + _cleanup_strv_free_ char **polkit_details = NULL, **args = NULL; + const char *user = NULL, *path = NULL; /* gcc complains about uninitialized variables */ + Machine *machine; + int r, ptmx_fd_idx; + + assert(link); + assert(parameters); + + r = sd_varlink_set_allow_fd_passing_output(link, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.mode == MACHINE_OPEN_MODE_SHELL) { + /* json_dispatch_const_user_group_name() does valid_user_group_name(p.user) */ + /* json_dispatch_path() does path_is_absolute(p.path) */ + /* json_dispatch_strv_environment() does validation of p.env */ + + user = p.user ?: "root"; + path = p.path ?: machine_default_shell_path(); + args = !p.path ? machine_default_shell_args(user) : strv_isempty(p.args) ? strv_new(path) : TAKE_PTR(p.args); + if (!args) + return -ENOMEM; + + command_line = strv_join(args, " "); + if (!command_line) + return -ENOMEM; + } + + r = lookup_machine_by_name_or_pidref(link, manager, p.name, &p.pidref, &machine); + if (r == -ESRCH) + return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); + if (r < 0) + return r; + + polkit_details = machine_open_polkit_details(p.mode, machine->name, user, path, command_line); + r = varlink_verify_polkit_async( + link, + manager->bus, + machine_open_polkit_action(p.mode, machine->class), + (const char**) polkit_details, + &manager->polkit_registry); + if (r <= 0) + return r; + + ptmx_fd = machine_openpt(machine, O_RDWR|O_NOCTTY|O_CLOEXEC, &ptmx_name); + if (ERRNO_IS_NEG_NOT_SUPPORTED(ptmx_fd)) + return sd_varlink_error(link, "io.systemd.Machine.NotSupported", NULL); + if (ptmx_fd < 0) + return log_debug_errno(ptmx_fd, "Failed to open pseudo terminal: %m"); + + switch (p.mode) { + case MACHINE_OPEN_MODE_TTY: + /* noop */ + break; + + case MACHINE_OPEN_MODE_LOGIN: + r = machine_start_getty(machine, ptmx_name, /* error = */ NULL); + if (r == -ENOENT) + return sd_varlink_error(link, "io.systemd.Machine.NoIPC", NULL); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return sd_varlink_error(link, "io.systemd.Machine.NotSupported", NULL); + if (r < 0) + return log_debug_errno(r, "Failed to start getty for machine '%s': %m", machine->name); + + break; + + case MACHINE_OPEN_MODE_SHELL: { + assert(user && path && args); /* to avoid gcc complaining about possible uninitialized variables */ + r = machine_start_shell(machine, ptmx_fd, ptmx_name, user, path, args, p.env, /* error = */ NULL); + if (r == -ENOENT) + return sd_varlink_error(link, "io.systemd.Machine.NoIPC", NULL); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return sd_varlink_error(link, "io.systemd.Machine.NotSupported", NULL); + if (r < 0) + return log_debug_errno(r, "Failed to start shell for machine '%s': %m", machine->name); + + break; + } + + default: + assert_not_reached(); + } + + ptmx_fd_idx = sd_varlink_push_fd(link, ptmx_fd); + /* no need to handle -EPERM because we do sd_varlink_set_allow_fd_passing_output() above */ + if (ptmx_fd_idx < 0) + return log_debug_errno(ptmx_fd_idx, "Failed to push file descriptor over varlink: %m"); + + TAKE_FD(ptmx_fd); + + r = sd_json_buildo( + &v, + SD_JSON_BUILD_PAIR_INTEGER("ptyFileDescriptor", ptmx_fd_idx), + JSON_BUILD_PAIR_STRING_NON_EMPTY("ptyPath", ptmx_name)); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} diff --git a/src/machine/machine-varlink.h b/src/machine/machine-varlink.h index 5b58b35d80..380f011dbe 100644 --- a/src/machine/machine-varlink.h +++ b/src/machine/machine-varlink.h @@ -24,3 +24,4 @@ int vl_method_register(sd_varlink *link, sd_json_variant *parameters, sd_varlink int vl_method_unregister_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/machine/machine.c b/src/machine/machine.c index 4da206cd5e..587c2267b6 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "bus-error.h" +#include "bus-internal.h" #include "bus-locator.h" #include "bus-unit-util.h" #include "bus-util.h" @@ -702,6 +703,276 @@ int machine_open_terminal(Machine *m, const char *path, int mode) { } } +static int machine_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { + int r; + + assert(m); + assert(ret); + + switch (m->class) { + + case MACHINE_HOST: + *ret = NULL; + return 0; + + case MACHINE_CONTAINER: { + _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; + char *address; + + r = sd_bus_new(&bus); + if (r < 0) + return log_debug_errno(r, "Failed to allocate new DBus: %m"); + + if (asprintf(&address, "x-machine-unix:pid=%" PID_PRI, m->leader.pid) < 0) + return -ENOMEM; + + bus->address = address; + bus->bus_client = true; + bus->trusted = false; + bus->runtime_scope = RUNTIME_SCOPE_SYSTEM; + + r = sd_bus_start(bus); + if (r == -ENOENT) + return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); + if (r < 0) + return r; + + *ret = TAKE_PTR(bus); + return 0; + } + + default: + return -EOPNOTSUPP; + } +} + +int machine_start_getty(Machine *m, const char *ptmx_name, sd_bus_error *error) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + sd_bus *container_bus = NULL; + const char *p, *getty; + int r; + + assert(m); + assert(ptmx_name); + + p = path_startswith(ptmx_name, "/dev/pts/"); + if (!p) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Path of pseudo TTY has unexpected prefix"); + + r = machine_bus_new(m, error, &allocated_bus); + if (r < 0) + return log_debug_errno(r, "Failed to create DBus to machine: %m"); + + container_bus = allocated_bus ?: m->manager->bus; + getty = strjoina("container-getty@", p, ".service"); + + r = bus_call_method(container_bus, bus_systemd_mgr, "StartUnit", error, /* reply = */ NULL, "ss", getty, "replace"); + if (r < 0) + return log_debug_errno(r, "Failed to StartUnit '%s' in container '%s': %m", getty, m->name); + + return 0; +} + +int machine_start_shell( + Machine *m, + int ptmx_fd, + const char *ptmx_name, + const char *user, + const char *path, + char **args, + char **env, + sd_bus_error *error) { + _cleanup_close_ int pty_fd = -EBADF; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *tm = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + const char *p, *utmp_id, *unit, *description; + sd_bus *container_bus = NULL; + int r; + + assert(m); + assert(ptmx_fd >= 0); + assert(ptmx_name); + + if (isempty(user) || isempty(path) || strv_isempty(args)) + return -EINVAL; + + p = path_startswith(ptmx_name, "/dev/pts/"); + utmp_id = path_startswith(ptmx_name, "/dev/"); + if (!p || !utmp_id) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Path of pseudo TTY has unexpected prefix"); + + /* First try to get an fd for the PTY peer via the new racefree ioctl(), directly. Otherwise go via + * joining the namespace, because it goes by path */ + pty_fd = pty_open_peer_racefree(ptmx_fd, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (ERRNO_IS_NEG_NOT_SUPPORTED(pty_fd)) + pty_fd = machine_open_terminal(m, ptmx_name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (pty_fd < 0) + return log_debug_errno(pty_fd, "Failed to open terminal: %m"); + + r = machine_bus_new(m, error, &allocated_bus); + if (r < 0) + return log_debug_errno(r, "Failed to create DBus to machine: %m"); + + container_bus = allocated_bus ?: m->manager->bus; + r = bus_message_new_method_call(container_bus, &tm, bus_systemd_mgr, "StartTransientUnit"); + if (r < 0) + return r; + + /* Name and mode */ + unit = strjoina("container-shell@", p, ".service"); + r = sd_bus_message_append(tm, "ss", unit, "fail"); + if (r < 0) + return r; + + /* Properties */ + r = sd_bus_message_open_container(tm, 'a', "(sv)"); + if (r < 0) + return r; + + description = strjoina("Shell for User ", user); + r = sd_bus_message_append(tm, + "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", + "Description", "s", description, + "StandardInputFileDescriptor", "h", pty_fd, + "StandardOutputFileDescriptor", "h", pty_fd, + "StandardErrorFileDescriptor", "h", pty_fd, + "SendSIGHUP", "b", true, + "IgnoreSIGPIPE", "b", false, + "KillMode", "s", "mixed", + "TTYPath", "s", ptmx_name, + "TTYReset", "b", true, + "UtmpIdentifier", "s", utmp_id, + "UtmpMode", "s", "user", + "PAMName", "s", "login", + "WorkingDirectory", "s", "-~"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "(sv)", "User", "s", user); + if (r < 0) + return r; + + if (!strv_isempty(env)) { + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "Environment"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, env); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + } + + /* Exec container */ + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, args); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "b", true); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + /* Auxiliary units */ + r = sd_bus_message_append(tm, "a(sa(sv))", 0); + if (r < 0) + return r; + + r = sd_bus_call(container_bus, tm, 0, error, NULL); + if (r < 0) + return r; + + return 0; +} + +char** machine_default_shell_args(const char *user) { + _cleanup_strv_free_ char **args = NULL; + int r; + + assert(user); + + args = new0(char*, 3 + 1); + if (!args) + return NULL; + + args[0] = strdup("sh"); + if (!args[0]) + return NULL; + + args[1] = strdup("-c"); + if (!args[1]) + return NULL; + + r = asprintf(&args[2], + "shell=$(getent passwd %s 2>/dev/null | { IFS=: read _ _ _ _ _ _ x; echo \"$x\"; })\n"\ + "exec \"${shell:-/bin/sh}\" -l", /* -l is means --login */ + user); + if (r < 0) { + args[2] = NULL; + return NULL; + } + + return TAKE_PTR(args); +} + void machine_release_unit(Machine *m) { assert(m); diff --git a/src/machine/machine.h b/src/machine/machine.h index debcb7d4b9..bf265143f0 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -102,6 +102,10 @@ KillWhom kill_whom_from_string(const char *s) _pure_; int machine_openpt(Machine *m, int flags, char **ret_slave); int machine_open_terminal(Machine *m, const char *path, int mode); +int machine_start_getty(Machine *m, const char *ptmx_name, sd_bus_error *error); +int machine_start_shell(Machine *m, int ptmx_fd, const char *ptmx_name, const char *user, const char *path, char **args, char **env, sd_bus_error *error); +#define machine_default_shell_path() ("/bin/sh") +char** machine_default_shell_args(const char *user); int machine_get_uid_shift(Machine *m, uid_t *ret); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 85cac71dda..616b972b37 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -773,6 +773,7 @@ static int manager_varlink_init_machine(Manager *m) { "io.systemd.Machine.Unregister", vl_method_unregister, "io.systemd.Machine.Terminate", vl_method_terminate, "io.systemd.Machine.Kill", vl_method_kill, + "io.systemd.Machine.Open", vl_method_open, "io.systemd.MachineImage.List", vl_method_list_images, "io.systemd.MachineImage.Update", vl_method_update_image, "io.systemd.MachineImage.Clone", vl_method_clone_image, diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 47eea9b19a..a63b907c74 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -493,40 +493,6 @@ static int json_dispatch_access_mode(const char *name, sd_json_variant *variant, return 0; } -static int json_dispatch_environment(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { - _cleanup_strv_free_ char **n = NULL; - char ***l = userdata; - int r; - - if (sd_json_variant_is_null(variant)) { - *l = strv_free(*l); - return 0; - } - - if (!sd_json_variant_is_array(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); - - for (size_t i = 0; i < sd_json_variant_elements(variant); i++) { - sd_json_variant *e; - const char *a; - - e = sd_json_variant_by_index(variant, i); - if (!sd_json_variant_is_string(e)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name)); - - assert_se(a = sd_json_variant_string(e)); - - if (!env_assignment_is_valid(a)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of environment variables.", strna(name)); - - r = strv_env_replace_strdup(&n, a); - if (r < 0) - return json_log_oom(variant, flags); - } - - return strv_free_and_replace(*l, n); -} - static int json_dispatch_locale(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { char **s = userdata; const char *n; @@ -1237,7 +1203,7 @@ static int dispatch_per_machine(const char *name, sd_json_variant *variant, sd_j { "location", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, location), 0 }, { "shell", SD_JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 }, { "umask", SD_JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 }, - { "environment", SD_JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 }, + { "environment", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_environment, offsetof(UserRecord, environment), 0 }, { "timeZone", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, time_zone), SD_JSON_STRICT }, { "preferredLanguage", SD_JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 }, { "additionalLanguages", SD_JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 }, @@ -1583,7 +1549,7 @@ int user_record_load(UserRecord *h, sd_json_variant *v, UserRecordLoadFlags load { "lastPasswordChangeUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(UserRecord, last_password_change_usec), 0 }, { "shell", SD_JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 }, { "umask", SD_JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 }, - { "environment", SD_JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 }, + { "environment", SD_JSON_VARIANT_ARRAY, json_dispatch_strv_environment, offsetof(UserRecord, environment), 0 }, { "timeZone", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(UserRecord, time_zone), SD_JSON_STRICT }, { "preferredLanguage", SD_JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 }, { "additionalLanguages", SD_JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 }, diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 3b44f80854..83a20f4f0e 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -95,12 +95,41 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_FIELD_COMMENT("Return the base UID/GID of the machine"), SD_VARLINK_DEFINE_OUTPUT(UIDShift, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); +static SD_VARLINK_DEFINE_ENUM_TYPE( + MachineOpenMode, + SD_VARLINK_FIELD_COMMENT("This mode allocates a pseudo TTY in the container and returns a file descriptor and its path. This is equivalent to transitioning into the container and invoking posix_openpt(3)."), + SD_VARLINK_DEFINE_ENUM_VALUE(tty), + SD_VARLINK_FIELD_COMMENT("This mode allocates a pseudo TTY in the container and ensures that a getty login prompt of the container is running on the other end. It returns the file descriptor of the PTY and the PTY path. This is useful for acquiring a pty with a login prompt from the container."), + SD_VARLINK_DEFINE_ENUM_VALUE(login), + SD_VARLINK_FIELD_COMMENT("This mode allocates a pseudo TTY in the container, as the specified user, and invokes the executable at the specified path with a list of arguments (starting from argv[0]) and an environment block. It then returns the file descriptor of the PTY and the PTY path."), + SD_VARLINK_DEFINE_ENUM_VALUE(shell)); + +static SD_VARLINK_DEFINE_METHOD( + Open, + VARLINK_DEFINE_MACHINE_LOOKUP_AND_POLKIT_INPUT_FIELDS, + SD_VARLINK_FIELD_COMMENT("There are three possible values: 'tty', 'login', and 'shell'. Please see description for each of the modes."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(mode, MachineOpenMode, 0), + SD_VARLINK_FIELD_COMMENT("See description of mode='shell'. Valid only when mode='shell'"), + SD_VARLINK_DEFINE_INPUT(user, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("See description of mode='shell'. Valid only when mode='shell'"), + SD_VARLINK_DEFINE_INPUT(path, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("See description of mode='shell'. Valid only when mode='shell'"), + SD_VARLINK_DEFINE_INPUT(args, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("See description of mode='shell'. Valid only when mode='shell'"), + SD_VARLINK_DEFINE_INPUT(environment, SD_VARLINK_STRING, SD_VARLINK_NULLABLE|SD_VARLINK_ARRAY), + SD_VARLINK_FIELD_COMMENT("File descriptor of the allocated pseudo TTY"), + SD_VARLINK_DEFINE_OUTPUT(ptyFileDescriptor, SD_VARLINK_INT, 0), + SD_VARLINK_FIELD_COMMENT("Path to the allocated pseudo TTY"), + SD_VARLINK_DEFINE_OUTPUT(ptyPath, SD_VARLINK_STRING, 0)); + static SD_VARLINK_DEFINE_ERROR(NoSuchMachine); static SD_VARLINK_DEFINE_ERROR(MachineExists); static SD_VARLINK_DEFINE_ERROR(NoPrivateNetworking); static SD_VARLINK_DEFINE_ERROR(NoOSReleaseInformation); static SD_VARLINK_DEFINE_ERROR(NoUIDShift); static SD_VARLINK_DEFINE_ERROR(NotAvailable); +static SD_VARLINK_DEFINE_ERROR(NotSupported); +static SD_VARLINK_DEFINE_ERROR(NoIPC); SD_VARLINK_DEFINE_INTERFACE( io_systemd_Machine, @@ -121,6 +150,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_method_Kill, SD_VARLINK_SYMBOL_COMMENT("List running machines"), &vl_method_List, + SD_VARLINK_SYMBOL_COMMENT("A enum field which defines way to open TTY for a machine"), + &vl_type_MachineOpenMode, + SD_VARLINK_SYMBOL_COMMENT("Allocates a pseudo TTY in the container in various modes"), + &vl_method_Open, SD_VARLINK_SYMBOL_COMMENT("No matching machine currently running"), &vl_error_NoSuchMachine, &vl_error_MachineExists, @@ -131,4 +164,8 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Machine uses a complex UID/GID mapping, cannot determine shift"), &vl_error_NoUIDShift, SD_VARLINK_SYMBOL_COMMENT("Requested information is not available"), - &vl_error_NotAvailable); + &vl_error_NotAvailable, + SD_VARLINK_SYMBOL_COMMENT("Requested operation is not supported"), + &vl_error_NotSupported, + SD_VARLINK_SYMBOL_COMMENT("There is no IPC service (such as system bus or varlink) in the container"), + &vl_error_NoIPC); diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index 1da08415df..f9cbd92bba 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -367,6 +367,25 @@ varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host"}' | grep 'acquireUIDShift') varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host", "acquireMetadata": "yes"}' | grep 'UIDShift' +# test io.systemd.Machine.Open + +# Reducing log level here is to work-around check in end.service (end.sh). Read https://github.com/systemd/systemd/pull/34867 for more details +systemctl service-log-level systemd-machined info +(! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host"}') +(! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": ""}') +(! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": null}') +(! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "foo"}') +systemctl service-log-level systemd-machined debug + +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "tty"}' +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "login"}' +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell"}' + +rm -f /tmp/none-existent-file +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/sh", "args": ["/bin/sh", "-c", "echo $FOO > /tmp/none-existent-file"], "environment": ["FOO=BAR"]}' +timeout 30 bash -c "until test -e /tmp/none-existent-file; do sleep .5; done" +grep -q "BAR" /tmp/none-existent-file + # test io.systemd.MachineImage.List varlinkctl --more call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{}' | grep 'long-running' varlinkctl --more call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{}' | grep '.host'