diff --git a/.mkosi/mkosi.fedora b/.mkosi/mkosi.fedora index c7505d19f6..e642c0b064 100644 --- a/.mkosi/mkosi.fedora +++ b/.mkosi/mkosi.fedora @@ -67,6 +67,7 @@ BuildPackages= m4 meson pam-devel + pcre2-devel pkgconfig python3-devel python3-lxml diff --git a/TODO b/TODO index 17022d912b..048270bf51 100644 --- a/TODO +++ b/TODO @@ -32,6 +32,18 @@ Features: * teach tmpfiles.d q/Q logic something sensible in the context of XFS/ext4 project quota +* introduce DefaultSlice= or so in system.conf that allows changing where we + place our units by default, i.e. change system.slice to something + else. Similar, ManagerSlice= should exist so that PID1's own scope unit could + be moved somewhere else too. Finally machined and logind should get similar + options so that it is possible to move user session scopes and machines to a + different slice too by default. Usecase: people who want to put resources on + the entire system, with the exception of one specific service. See: + https://lists.freedesktop.org/archives/systemd-devel/2018-February/040369.html + +* check what setting the login shell to /bin/false vs. /sbin/nologin means and + do the right thing in get_user_creds_clean() with it. + * maybe rework get_user_creds() to query the user database if $SHELL is used for root, but only then. diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 855ac7534a..aa41b3b686 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -850,17 +850,33 @@ int kill_and_sigcont(pid_t pid, int sig) { return r; } -int getenv_for_pid(pid_t pid, const char *field, char **_value) { +int getenv_for_pid(pid_t pid, const char *field, char **ret) { _cleanup_fclose_ FILE *f = NULL; char *value = NULL; - int r; bool done = false; - size_t l; const char *path; + size_t l; assert(pid >= 0); assert(field); - assert(_value); + assert(ret); + + if (pid == 0 || pid == getpid_cached()) { + const char *e; + + e = getenv(field); + if (!e) { + *ret = NULL; + return 0; + } + + value = strdup(e); + if (!value) + return -ENOMEM; + + *ret = value; + return 1; + } path = procfs_file_alloca(pid, "environ"); @@ -868,13 +884,13 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { if (!f) { if (errno == ENOENT) return -ESRCH; + return -errno; } (void) __fsetlocking(f, FSETLOCKING_BYCALLER); l = strlen(field); - r = 0; do { char line[LINE_MAX]; @@ -899,14 +915,14 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value) { if (!value) return -ENOMEM; - r = 1; - break; + *ret = value; + return 1; } } while (!done); - *_value = value; - return r; + *ret = NULL; + return 0; } bool pid_is_unwaited(pid_t pid) { diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 42336e8fdf..a897a6367d 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -48,6 +48,8 @@ #include "log.h" #include "macro.h" #include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" #include "process-util.h" #include "socket-util.h" #include "stat-util.h" @@ -56,14 +58,20 @@ #include "terminal-util.h" #include "time-util.h" #include "util.h" -#include "path-util.h" static volatile unsigned cached_columns = 0; static volatile unsigned cached_lines = 0; +static volatile int cached_on_tty = -1; +static volatile int cached_colors_enabled = -1; +static volatile int cached_underline_enabled = -1; + int chvt(int vt) { _cleanup_close_ int fd; + /* Switch to the specified vt number. If the VT is specified <= 0 switch to the VT the kernel log messages go, + * if that's configured. */ + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); if (fd < 0) return -errno; @@ -323,8 +331,8 @@ int reset_terminal(const char *name) { } int open_terminal(const char *name, int mode) { - int fd, r; unsigned c = 0; + int fd; /* * If a TTY is in the process of being closed opening it might @@ -354,8 +362,7 @@ int open_terminal(const char *name, int mode) { c++; } - r = isatty(fd); - if (r == 0) { + if (isatty(fd) <= 0) { safe_close(fd); return -ENOTTY; } @@ -365,44 +372,36 @@ int open_terminal(const char *name, int mode) { int acquire_terminal( const char *name, - bool fail, - bool force, - bool ignore_tiocstty_eperm, + AcquireTerminalFlags flags, usec_t timeout) { - int fd = -1, notify = -1, r = 0, wd = -1; - usec_t ts = 0; + _cleanup_close_ int notify = -1, fd = -1; + usec_t ts = USEC_INFINITY; + int r, wd = -1; assert(name); + assert(IN_SET(flags & ~ACQUIRE_TERMINAL_PERMISSIVE, ACQUIRE_TERMINAL_TRY, ACQUIRE_TERMINAL_FORCE, ACQUIRE_TERMINAL_WAIT)); - /* We use inotify to be notified when the tty is closed. We - * create the watch before checking if we can actually acquire - * it, so that we don't lose any event. + /* We use inotify to be notified when the tty is closed. We create the watch before checking if we can actually + * acquire it, so that we don't lose any event. * - * Note: strictly speaking this actually watches for the - * device being closed, it does *not* really watch whether a - * tty loses its controlling process. However, unless some - * rogue process uses TIOCNOTTY on /dev/tty *after* closing - * its tty otherwise this will not become a problem. As long - * as the administrator makes sure not configure any service - * on the same tty as an untrusted user this should not be a - * problem. (Which he probably should not do anyway.) */ + * Note: strictly speaking this actually watches for the device being closed, it does *not* really watch + * whether a tty loses its controlling process. However, unless some rogue process uses TIOCNOTTY on /dev/tty + * *after* closing its tty otherwise this will not become a problem. As long as the administrator makes sure + * not configure any service on the same tty as an untrusted user this should not be a problem. (Which he + * probably should not do anyway.) */ - if (timeout != USEC_INFINITY) - ts = now(CLOCK_MONOTONIC); - - if (!fail && !force) { + if ((flags & ~ACQUIRE_TERMINAL_PERMISSIVE) == ACQUIRE_TERMINAL_WAIT) { notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0)); - if (notify < 0) { - r = -errno; - goto fail; - } + if (notify < 0) + return -errno; wd = inotify_add_watch(notify, name, IN_CLOSE); - if (wd < 0) { - r = -errno; - goto fail; - } + if (wd < 0) + return -errno; + + if (timeout != USEC_INFINITY) + ts = now(CLOCK_MONOTONIC); } for (;;) { @@ -414,41 +413,43 @@ int acquire_terminal( if (notify >= 0) { r = flush_fd(notify); if (r < 0) - goto fail; + return r; } - /* We pass here O_NOCTTY only so that we can check the return - * value TIOCSCTTY and have a reliable way to figure out if we - * successfully became the controlling process of the tty */ + /* We pass here O_NOCTTY only so that we can check the return value TIOCSCTTY and have a reliable way + * to figure out if we successfully became the controlling process of the tty */ fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); if (fd < 0) return fd; - /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed - * if we already own the tty. */ + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed if we already own the tty. */ assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); /* First, try to get the tty */ - if (ioctl(fd, TIOCSCTTY, force) < 0) - r = -errno; + r = ioctl(fd, TIOCSCTTY, + (flags & ~ACQUIRE_TERMINAL_PERMISSIVE) == ACQUIRE_TERMINAL_FORCE) < 0 ? -errno : 0; + /* Reset signal handler to old value */ assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); - /* Sometimes, it makes sense to ignore TIOCSCTTY - * returning EPERM, i.e. when very likely we already - * are have this controlling terminal. */ - if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) - r = 0; - - if (r < 0 && (force || fail || r != -EPERM)) - goto fail; - + /* Success? Exit the loop now! */ if (r >= 0) break; - assert(!fail); - assert(!force); + /* Any failure besides -EPERM? Fail, regardless of the mode. */ + if (r != -EPERM) + return r; + + if (flags & ACQUIRE_TERMINAL_PERMISSIVE) /* If we are in permissive mode, then EPERM is fine, turn this + * into a success. Note that EPERM is also returned if we + * already are the owner of the TTY. */ + break; + + if (flags != ACQUIRE_TERMINAL_WAIT) /* If we are in TRY or FORCE mode, then propagate EPERM as EPERM */ + return r; + assert(notify >= 0); + assert(wd >= 0); for (;;) { union inotify_event_buffer buffer; @@ -458,20 +459,17 @@ int acquire_terminal( if (timeout != USEC_INFINITY) { usec_t n; + assert(ts != USEC_INFINITY); + n = now(CLOCK_MONOTONIC); - if (ts + timeout < n) { - r = -ETIMEDOUT; - goto fail; - } + if (ts + timeout < n) + return -ETIMEDOUT; r = fd_wait_for_event(notify, POLLIN, ts + timeout - n); if (r < 0) - goto fail; - - if (r == 0) { - r = -ETIMEDOUT; - goto fail; - } + return r; + if (r == 0) + return -ETIMEDOUT; } l = read(notify, &buffer, sizeof(buffer)); @@ -479,34 +477,27 @@ int acquire_terminal( if (IN_SET(errno, EINTR, EAGAIN)) continue; - r = -errno; - goto fail; + return -errno; } FOREACH_INOTIFY_EVENT(e, buffer, l) { - if (e->wd != wd || !(e->mask & IN_CLOSE)) { - r = -EIO; - goto fail; - } + if (e->mask & IN_Q_OVERFLOW) /* If we hit an inotify queue overflow, simply check if the terminal is up for grabs now. */ + break; + + if (e->wd != wd || !(e->mask & IN_CLOSE)) /* Safety checks */ + return -EIO; } break; } - /* We close the tty fd here since if the old session - * ended our handle will be dead. It's important that - * we do this after sleeping, so that we don't enter - * an endless loop. */ + /* We close the tty fd here since if the old session ended our handle will be dead. It's important that + * we do this after sleeping, so that we don't enter an endless loop. */ fd = safe_close(fd); } - safe_close(notify); - - return fd; - -fail: - safe_close(fd); - safe_close(notify); + r = fd; + fd = -1; return r; } @@ -519,7 +510,7 @@ int release_terminal(void) { _cleanup_close_ int fd = -1; struct sigaction sa_old; - int r = 0; + int r; fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); if (fd < 0) @@ -529,8 +520,7 @@ int release_terminal(void) { * by our own TIOCNOTTY */ assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); - if (ioctl(fd, TIOCNOTTY) < 0) - r = -errno; + r = ioctl(fd, TIOCNOTTY) < 0 ? -errno : 0; assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); @@ -630,7 +620,7 @@ int make_console_stdio(void) { /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ - fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY); + fd = acquire_terminal("/dev/console", ACQUIRE_TERMINAL_FORCE|ACQUIRE_TERMINAL_PERMISSIVE, USEC_INFINITY); if (fd < 0) return log_error_errno(fd, "Failed to acquire terminal: %m"); @@ -642,6 +632,8 @@ int make_console_stdio(void) { if (r < 0) return log_error_errno(r, "Failed to duplicate terminal fd: %m"); + reset_terminal_feature_caches(); + return 0; } @@ -680,57 +672,80 @@ int vtnr_from_tty(const char *tty) { return i; } -char *resolve_dev_console(char **active) { + int resolve_dev_console(char **ret) { + _cleanup_free_ char *active = NULL; char *tty; + int r; - /* Resolve where /dev/console is pointing to, if /sys is actually ours - * (i.e. not read-only-mounted which is a sign for container setups) */ + assert(ret); + + /* Resolve where /dev/console is pointing to, if /sys is actually ours (i.e. not read-only-mounted which is a + * sign for container setups) */ if (path_is_read_only_fs("/sys") > 0) - return NULL; + return -ENOMEDIUM; - if (read_one_line_file("/sys/class/tty/console/active", active) < 0) - return NULL; + r = read_one_line_file("/sys/class/tty/console/active", &active); + if (r < 0) + return r; - /* If multiple log outputs are configured the last one is what - * /dev/console points to */ - tty = strrchr(*active, ' '); + /* If multiple log outputs are configured the last one is what /dev/console points to */ + tty = strrchr(active, ' '); if (tty) tty++; else - tty = *active; + tty = active; if (streq(tty, "tty0")) { - char *tmp; + active = mfree(active); /* Get the active VC (e.g. tty1) */ - if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) { - free(*active); - tty = *active = tmp; - } + r = read_one_line_file("/sys/class/tty/tty0/active", &active); + if (r < 0) + return r; + + tty = active; } - return tty; + if (tty == active) { + *ret = active; + active = NULL; + } else { + char *tmp; + + tmp = strdup(tty); + if (!tmp) + return -ENOMEM; + + *ret = tmp; + } + + return 0; } -int get_kernel_consoles(char ***consoles) { - _cleanup_strv_free_ char **con = NULL; +int get_kernel_consoles(char ***ret) { + _cleanup_strv_free_ char **l = NULL; _cleanup_free_ char *line = NULL; - const char *active; + const char *p; int r; - assert(consoles); + assert(ret); + + /* If we /sys is mounted read-only this means we are running in some kind of container environment. In that + * case /sys would reflect the host system, not us, hence ignore the data we can read from it. */ + if (path_is_read_only_fs("/sys") > 0) + goto fallback; r = read_one_line_file("/sys/class/tty/console/active", &line); if (r < 0) return r; - active = line; + p = line; for (;;) { _cleanup_free_ char *tty = NULL; char *path; - r = extract_first_word(&active, &tty, NULL, 0); + r = extract_first_word(&p, &tty, NULL, 0); if (r < 0) return r; if (r == 0) @@ -753,35 +768,44 @@ int get_kernel_consoles(char ***consoles) { continue; } - r = strv_consume(&con, path); + r = strv_consume(&l, path); if (r < 0) return r; } - if (strv_isempty(con)) { + if (strv_isempty(l)) { log_debug("No devices found for system console"); - - r = strv_extend(&con, "/dev/console"); - if (r < 0) - return r; + goto fallback; } - *consoles = con; - con = NULL; + *ret = l; + l = NULL; + + return 0; + +fallback: + r = strv_extend(&l, "/dev/console"); + if (r < 0) + return r; + + *ret = l; + l = NULL; + return 0; } bool tty_is_vc_resolve(const char *tty) { - _cleanup_free_ char *active = NULL; + _cleanup_free_ char *resolved = NULL; assert(tty); tty = skip_dev_prefix(tty); if (streq(tty, "console")) { - tty = resolve_dev_console(&active); - if (!tty) + if (resolve_dev_console(&resolved) < 0) return false; + + tty = resolved; } return tty_is_vc(tty); @@ -807,7 +831,7 @@ unsigned columns(void) { const char *e; int c; - if (_likely_(cached_columns > 0)) + if (cached_columns > 0) return cached_columns; c = 0; @@ -841,7 +865,7 @@ unsigned lines(void) { const char *e; int l; - if (_likely_(cached_lines > 0)) + if (cached_lines > 0) return cached_lines; l = 0; @@ -865,10 +889,17 @@ void columns_lines_cache_reset(int signum) { cached_lines = 0; } -bool on_tty(void) { - static int cached_on_tty = -1; +void reset_terminal_feature_caches(void) { + cached_columns = 0; + cached_lines = 0; - if (_unlikely_(cached_on_tty < 0)) + cached_colors_enabled = -1; + cached_underline_enabled = -1; + cached_on_tty = -1; +} + +bool on_tty(void) { + if (cached_on_tty < 0) cached_on_tty = isatty(STDOUT_FILENO) > 0; return cached_on_tty; @@ -879,7 +910,7 @@ int make_stdio(int fd) { assert(fd >= 0); - if (dup2(fd, STDIN_FILENO) < 0 && r >= 0) + if (dup2(fd, STDIN_FILENO) < 0) r = -errno; if (dup2(fd, STDOUT_FILENO) < 0 && r >= 0) r = -errno; @@ -896,13 +927,17 @@ int make_stdio(int fd) { } int make_null_stdio(void) { - int null_fd; + int null_fd, r; null_fd = open("/dev/null", O_RDWR|O_NOCTTY|O_CLOEXEC); if (null_fd < 0) return -errno; - return make_stdio(null_fd); + r = make_stdio(null_fd); + + reset_terminal_feature_caches(); + + return r; } int getttyname_malloc(int fd, char **ret) { @@ -1205,38 +1240,63 @@ bool terminal_is_dumb(void) { } bool colors_enabled(void) { - static int enabled = -1; - if (_unlikely_(enabled < 0)) { + /* Returns true if colors are considered supported on our stdout. For that we check $SYSTEMD_COLORS first + * (which is the explicit way to turn off/on colors). If that didn't work we turn off colors unless we are on a + * TTY. And if we are on a TTY we turn it off if $TERM is set to "dumb". There's one special tweak though: if + * we are PID 1 then we do not check whether we are connected to a TTY, because we don't keep /dev/console open + * continously due to fear of SAK, and hence things are a bit weird. */ + + if (cached_colors_enabled < 0) { int val; val = getenv_bool("SYSTEMD_COLORS"); if (val >= 0) - enabled = val; + cached_colors_enabled = val; else if (getpid_cached() == 1) /* PID1 outputs to the console without holding it open all the time */ - enabled = !getenv_terminal_is_dumb(); + cached_colors_enabled = !getenv_terminal_is_dumb(); else - enabled = !terminal_is_dumb(); + cached_colors_enabled = !terminal_is_dumb(); } - return enabled; + return cached_colors_enabled; +} + +bool dev_console_colors_enabled(void) { + _cleanup_free_ char *s = NULL; + int b; + + /* Returns true if we assume that color is supported on /dev/console. + * + * For that we first check if we explicitly got told to use colors or not, by checking $SYSTEMD_COLORS. If that + * didn't tell us anything we check whether PID 1 has $TERM set, and if not whether $TERM is set on the kernel + * command line. If we find $TERM set we assume color if it's not set to "dumb", similar to regular + * colors_enabled() operates. */ + + b = getenv_bool("SYSTEMD_COLORS"); + if (b >= 0) + return b; + + if (getenv_for_pid(1, "TERM", &s) <= 0) + (void) proc_cmdline_get_key("TERM", 0, &s); + + return !streq_ptr(s, "dumb"); } bool underline_enabled(void) { - static int enabled = -1; - if (enabled < 0) { + if (cached_underline_enabled < 0) { /* The Linux console doesn't support underlining, turn it off, but only there. */ - if (!colors_enabled()) - enabled = false; + if (colors_enabled()) + cached_underline_enabled = !streq_ptr(getenv("TERM"), "linux"); else - enabled = !streq_ptr(getenv("TERM"), "linux"); + cached_underline_enabled = false; } - return enabled; + return cached_underline_enabled; } int vt_default_utf8(void) { diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index e82719b11b..f6e6020b66 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -52,7 +52,23 @@ int reset_terminal_fd(int fd, bool switch_to_text); int reset_terminal(const char *name); int open_terminal(const char *name, int mode); -int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm, usec_t timeout); + +/* Flags for tweaking the way we become the controlling process of a terminal. */ +typedef enum AcquireTerminalFlags { + /* Try to become the controlling process of the TTY. If we can't return -EPERM. */ + ACQUIRE_TERMINAL_TRY = 0, + + /* Tell the kernel to forcibly make us the controlling process of the TTY. Returns -EPERM if the kernel doesn't allow that. */ + ACQUIRE_TERMINAL_FORCE = 1, + + /* If we can't become the controlling process of the TTY right-away, then wait until we can. */ + ACQUIRE_TERMINAL_WAIT = 2, + + /* Pick one of the above, and then OR this flag in, in order to request permissive behaviour, if we can't become controlling process then don't mind */ + ACQUIRE_TERMINAL_PERMISSIVE = 4, +} AcquireTerminalFlags; + +int acquire_terminal(const char *name, AcquireTerminalFlags flags, usec_t timeout); int release_terminal(void); int terminal_vhangup_fd(int fd); @@ -66,8 +82,8 @@ int ask_string(char **ret, const char *text, ...) _printf_(2, 3); int vt_disallocate(const char *name); -char *resolve_dev_console(char **active); -int get_kernel_consoles(char ***consoles); +int resolve_dev_console(char **ret); +int get_kernel_consoles(char ***ret); bool tty_is_vc(const char *tty); bool tty_is_vc_resolve(const char *tty); bool tty_is_console(const char *tty) _pure_; @@ -82,12 +98,15 @@ int fd_columns(int fd); unsigned columns(void); int fd_lines(int fd); unsigned lines(void); + void columns_lines_cache_reset(int _unused_ signum); +void reset_terminal_feature_caches(void); bool on_tty(void); bool terminal_is_dumb(void); bool colors_enabled(void); bool underline_enabled(void); +bool dev_console_colors_enabled(void); #define DEFINE_ANSI_FUNC(name, NAME) \ static inline const char *ansi_##name(void) { \ diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 4da9a405cb..b17f420264 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -408,3 +408,22 @@ int utf8_encoded_valid_unichar(const char *str) { return len; } + +size_t utf8_n_codepoints(const char *str) { + size_t n = 0; + + /* Returns the number of UTF-8 codepoints in this string, or (size_t) -1 if the string is not valid UTF-8. */ + + while (*str != 0) { + int k; + + k = utf8_encoded_valid_unichar(str); + if (k < 0) + return (size_t) -1; + + str += k; + n++; + } + + return n; +} diff --git a/src/basic/utf8.h b/src/basic/utf8.h index b0a7485aed..7128615181 100644 --- a/src/basic/utf8.h +++ b/src/basic/utf8.h @@ -59,3 +59,5 @@ static inline bool utf16_is_trailing_surrogate(char16_t c) { static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) { return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000; } + +size_t utf8_n_codepoints(const char *str); diff --git a/src/core/execute.c b/src/core/execute.c index be9ef20725..bebd4eca80 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -509,9 +509,9 @@ static int setup_input( int fd; fd = acquire_terminal(exec_context_tty_path(context), - i == EXEC_INPUT_TTY_FAIL, - i == EXEC_INPUT_TTY_FORCE, - false, + i == EXEC_INPUT_TTY_FAIL ? ACQUIRE_TERMINAL_TRY : + i == EXEC_INPUT_TTY_FORCE ? ACQUIRE_TERMINAL_FORCE : + ACQUIRE_TERMINAL_WAIT, USEC_INFINITY); if (fd < 0) return fd; @@ -753,7 +753,7 @@ static int setup_confirm_stdio(const char *vc, int *_saved_stdin, int *_saved_st if (saved_stdout < 0) return -errno; - fd = acquire_terminal(vc, false, false, false, DEFAULT_CONFIRM_USEC); + fd = acquire_terminal(vc, ACQUIRE_TERMINAL_WAIT, DEFAULT_CONFIRM_USEC); if (fd < 0) return fd; @@ -3871,8 +3871,7 @@ static int exec_context_load_environment(const Unit *unit, const ExecContext *c, } static bool tty_may_match_dev_console(const char *tty) { - _cleanup_free_ char *active = NULL; - char *console; + _cleanup_free_ char *resolved = NULL; if (!tty) return true; @@ -3883,13 +3882,11 @@ static bool tty_may_match_dev_console(const char *tty) { if (streq(tty, "console")) return true; - console = resolve_dev_console(&active); - /* if we could not resolve, assume it may */ - if (!console) - return true; + if (resolve_dev_console(&resolved) < 0) + return true; /* if we could not resolve, assume it may */ /* "tty0" means the active VC, so it may be the same sometimes */ - return streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty)); + return streq(resolved, tty) || (streq(resolved, "tty0") && tty_is_vc(tty)); } bool exec_context_may_touch_console(const ExecContext *ec) { diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 262e520d56..effa092ec9 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -558,7 +558,7 @@ static int prompt_root_password(void) { for (;;) { _cleanup_string_free_erase_ char *a = NULL, *b = NULL; - r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a); + r = ask_password_tty(-1, msg1, NULL, 0, 0, NULL, &a); if (r < 0) return log_error_errno(r, "Failed to query root password: %m"); @@ -567,7 +567,7 @@ static int prompt_root_password(void) { break; } - r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b); + r = ask_password_tty(-1, msg2, NULL, 0, 0, NULL, &b); if (r < 0) return log_error_errno(r, "Failed to query root password: %m"); diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 99d6a9b143..a0ee05e2d8 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -201,7 +201,29 @@ static void backspace_chars(int ttyfd, size_t p) { } } +static void backspace_string(int ttyfd, const char *str) { + size_t m; + + assert(str); + + if (ttyfd < 0) + return; + + /* Backspaces back for enough characters to entirely undo printing of the specified string. */ + + m = utf8_n_codepoints(str); + if (m == (size_t) -1) + m = strlen(str); /* Not a valid UTF-8 string? If so, let's backspace the number of bytes output. Most + * likely this happened because we are not in an UTF-8 locale, and in that case that + * is the correct thing to do. And even if it's not, terminals tend to stop + * backspacing at the leftmost column, hence backspacing too much should be mostly + * OK. */ + + backspace_chars(ttyfd, m); +} + int ask_password_tty( + int ttyfd, const char *message, const char *keyname, usec_t until, @@ -209,19 +231,20 @@ int ask_password_tty( const char *flag_file, char **ret) { - struct termios old_termios, new_termios; - char passphrase[LINE_MAX + 1] = {}, *x; - size_t p = 0, codepoint = 0; - int r; - _cleanup_close_ int ttyfd = -1, notify = -1; - struct pollfd pollfd[2]; - bool reset_tty = false; - bool dirty = false; enum { POLL_TTY, - POLL_INOTIFY + POLL_INOTIFY, + _POLL_MAX, }; + bool reset_tty = false, dirty = false, use_color = false; + _cleanup_close_ int cttyfd = -1, notify = -1; + struct termios old_termios, new_termios; + char passphrase[LINE_MAX + 1] = {}, *x; + struct pollfd pollfd[_POLL_MAX]; + size_t p = 0, codepoint = 0; + int r; + assert(ret); if (flags & ASK_PASSWORD_NO_TTY) @@ -232,33 +255,34 @@ int ask_password_tty( if (flag_file) { notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); - if (notify < 0) { - r = -errno; - goto finish; - } + if (notify < 0) + return -errno; - if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { - r = -errno; - goto finish; - } + if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) + return -errno; } - ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); + /* If the caller didn't specify a TTY, then use the controlling tty, if we can. */ + if (ttyfd < 0) + ttyfd = cttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (ttyfd >= 0) { + if (tcgetattr(ttyfd, &old_termios) < 0) + return -errno; - if (tcgetattr(ttyfd, &old_termios) < 0) { - r = -errno; - goto finish; - } + if (flags & ASK_PASSWORD_CONSOLE_COLOR) + use_color = dev_console_colors_enabled(); + else + use_color = colors_enabled(); - if (colors_enabled()) - loop_write(ttyfd, ANSI_HIGHLIGHT, - STRLEN(ANSI_HIGHLIGHT), false); - loop_write(ttyfd, message, strlen(message), false); - loop_write(ttyfd, " ", 1, false); - if (colors_enabled()) - loop_write(ttyfd, ANSI_NORMAL, STRLEN(ANSI_NORMAL), - false); + if (use_color) + (void) loop_write(ttyfd, ANSI_HIGHLIGHT, STRLEN(ANSI_HIGHLIGHT), false); + + (void) loop_write(ttyfd, message, strlen(message), false); + (void) loop_write(ttyfd, " ", 1, false); + + if (use_color) + (void) loop_write(ttyfd, ANSI_NORMAL, STRLEN(ANSI_NORMAL), false); new_termios = old_termios; new_termios.c_lflag &= ~(ICANON|ECHO); @@ -273,16 +297,19 @@ int ask_password_tty( reset_tty = true; } - zero(pollfd); - pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO; - pollfd[POLL_TTY].events = POLLIN; - pollfd[POLL_INOTIFY].fd = notify; - pollfd[POLL_INOTIFY].events = POLLIN; + pollfd[POLL_TTY] = (struct pollfd) { + .fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO, + .events = POLLIN, + }; + pollfd[POLL_INOTIFY] = (struct pollfd) { + .fd = notify, + .events = POLLIN, + }; for (;;) { - char c; int sleep_for = -1, k; ssize_t n; + char c; if (until > 0) { usec_t y; @@ -294,7 +321,7 @@ int ask_password_tty( goto finish; } - sleep_for = (int) ((until - y) / USEC_PER_MSEC); + sleep_for = (int) DIV_ROUND_UP(until - y, USEC_PER_MSEC); } if (flag_file) @@ -329,72 +356,99 @@ int ask_password_tty( r = -errno; goto finish; - } else if (n == 0) + } + + /* We treat EOF, newline and NUL byte all as valid end markers */ + if (n == 0 || c == '\n' || c == 0) break; - if (c == '\n') - break; - else if (c == 21) { /* C-u */ + if (c == 21) { /* C-u */ if (!(flags & ASK_PASSWORD_SILENT)) - backspace_chars(ttyfd, p); - p = 0; + backspace_string(ttyfd, passphrase); + + explicit_bzero(passphrase, sizeof(passphrase)); + p = codepoint = 0; } else if (IN_SET(c, '\b', 127)) { if (p > 0) { + size_t q; if (!(flags & ASK_PASSWORD_SILENT)) backspace_chars(ttyfd, 1); - p--; + /* Remove a full UTF-8 codepoint from the end. For that, figure out where the last one + * begins */ + q = 0; + for (;;) { + size_t z; + + z = utf8_encoded_valid_unichar(passphrase + q); + if (z == 0) { + q = (size_t) -1; /* Invalid UTF8! */ + break; + } + + if (q + z >= p) /* This one brings us over the edge */ + break; + + q += z; + } + + p = codepoint = q == (size_t) -1 ? p - 1 : q; + explicit_bzero(passphrase + p, sizeof(passphrase) - p); + } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) { flags |= ASK_PASSWORD_SILENT; - /* There are two ways to enter silent - * mode. Either by pressing backspace - * as first key (and only as first - * key), or ... */ + /* There are two ways to enter silent mode. Either by pressing backspace as first key + * (and only as first key), or ... */ + if (ttyfd >= 0) - loop_write(ttyfd, "(no echo) ", 10, false); + (void) loop_write(ttyfd, "(no echo) ", 10, false); } else if (ttyfd >= 0) - loop_write(ttyfd, "\a", 1, false); + (void) loop_write(ttyfd, "\a", 1, false); } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) { - backspace_chars(ttyfd, p); + backspace_string(ttyfd, passphrase); flags |= ASK_PASSWORD_SILENT; /* ... or by pressing TAB at any time. */ if (ttyfd >= 0) - loop_write(ttyfd, "(no echo) ", 10, false); - } else { - if (p >= sizeof(passphrase)-1) { - loop_write(ttyfd, "\a", 1, false); - continue; - } + (void) loop_write(ttyfd, "(no echo) ", 10, false); + } else if (p >= sizeof(passphrase)-1) { + + /* Reached the size limit */ + if (ttyfd >= 0) + (void) loop_write(ttyfd, "\a", 1, false); + + } else { passphrase[p++] = c; if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) { + /* Check if we got a complete UTF-8 character now. If so, let's output one '*'. */ n = utf8_encoded_valid_unichar(passphrase + codepoint); if (n >= 0) { codepoint = p; - loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); + (void) loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); } } dirty = true; } + /* Let's forget this char, just to not keep needlessly copies of key material around */ c = 'x'; } x = strndup(passphrase, p); - explicit_bzero(passphrase, p); + explicit_bzero(passphrase, sizeof(passphrase)); if (!x) { r = -ENOMEM; goto finish; @@ -408,8 +462,8 @@ int ask_password_tty( finish: if (ttyfd >= 0 && reset_tty) { - loop_write(ttyfd, "\n", 1, false); - tcsetattr(ttyfd, TCSADRAIN, &old_termios); + (void) loop_write(ttyfd, "\n", 1, false); + (void) tcsetattr(ttyfd, TCSADRAIN, &old_termios); } return r; @@ -715,7 +769,7 @@ int ask_password_auto( if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) { char *s = NULL, **l = NULL; - r = ask_password_tty(message, keyname, until, flags, NULL, &s); + r = ask_password_tty(-1, message, keyname, until, flags, NULL, &s); if (r < 0) return r; diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h index f3ca6743a9..4b2eb3fe92 100644 --- a/src/shared/ask-password-api.h +++ b/src/shared/ask-password-api.h @@ -25,15 +25,16 @@ #include "time-util.h" typedef enum AskPasswordFlags { - ASK_PASSWORD_ACCEPT_CACHED = 1, - ASK_PASSWORD_PUSH_CACHE = 2, - ASK_PASSWORD_ECHO = 4, /* show the password literally while reading, instead of "*" */ - ASK_PASSWORD_SILENT = 8, /* do no show any password at all while reading */ - ASK_PASSWORD_NO_TTY = 16, - ASK_PASSWORD_NO_AGENT = 32, + ASK_PASSWORD_ACCEPT_CACHED = 1U << 0, + ASK_PASSWORD_PUSH_CACHE = 1U << 1, + ASK_PASSWORD_ECHO = 1U << 2, /* show the password literally while reading, instead of "*" */ + ASK_PASSWORD_SILENT = 1U << 3, /* do no show any password at all while reading */ + ASK_PASSWORD_NO_TTY = 1U << 4, + ASK_PASSWORD_NO_AGENT = 1U << 5, + ASK_PASSWORD_CONSOLE_COLOR = 1U << 6, /* Use color if /dev/console points to a console that supports color */ } AskPasswordFlags; -int ask_password_tty(const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret); +int ask_password_tty(int tty_fd, const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret); int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret); int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); diff --git a/src/test/test-ask-password-api.c b/src/test/test-ask-password-api.c index da44465821..562a774531 100644 --- a/src/test/test-ask-password-api.c +++ b/src/test/test-ask-password-api.c @@ -26,7 +26,7 @@ static void ask_password(void) { int r; _cleanup_free_ char *ret; - r = ask_password_tty("hello?", "da key", 0, 0, NULL, &ret); + r = ask_password_tty(-1, "hello?", "da key", 0, 0, NULL, &ret); assert(r >= 0); log_info("Got %s", ret); diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 9dfb0d80de..c52a19dc37 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -254,6 +254,7 @@ static int send_passwords(const char *socket_name, char **passwords) { union sockaddr_union sa = { .un.sun_family = AF_UNIX }; size_t packet_length = 1; char **p, *d; + ssize_t n; int r; assert(socket_name); @@ -279,9 +280,13 @@ static int send_passwords(const char *socket_name, char **passwords) { strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); - r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un)); - if (r < 0) + n = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (n < 0) { r = log_debug_errno(errno, "sendto(): %m"); + goto finish; + } + + r = (int) n; finish: explicit_bzero(packet, packet_length); @@ -363,18 +368,21 @@ static int parse_password(const char *filename, char **wall) { int tty_fd = -1; if (arg_console) { - const char *con = arg_device ? arg_device : "/dev/console"; + const char *con = arg_device ?: "/dev/console"; - tty_fd = acquire_terminal(con, false, false, false, USEC_INFINITY); + tty_fd = acquire_terminal(con, ACQUIRE_TERMINAL_WAIT, USEC_INFINITY); if (tty_fd < 0) - return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m"); + return log_error_errno(tty_fd, "Failed to acquire %s: %m", con); r = reset_terminal_fd(tty_fd, true); if (r < 0) log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); } - r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password); + r = ask_password_tty(tty_fd, message, NULL, not_after, + (echo ? ASK_PASSWORD_ECHO : 0) | + (arg_console ? ASK_PASSWORD_CONSOLE_COLOR : 0), + filename, &password); if (arg_console) { tty_fd = safe_close(tty_fd);