Merge pull request #13632 from fbuihuu/ask-password-some-reworks

Ask password some reworks
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2019-10-08 09:20:40 +02:00 committed by GitHub
commit e0a0c64cb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 347 additions and 283 deletions

View file

@ -660,6 +660,21 @@ int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
return r;
}
int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask) {
if (inotify_add_watch(fd, pathname, mask) < 0) {
const char *reason;
if (errno == ENOSPC)
reason = "inotify watch limit reached";
else
reason = strerror_safe(errno);
return log_error_errno(errno, "Failed to add a watch for %s: %s", pathname, reason);
}
return 0;
}
static bool unsafe_transition(const struct stat *a, const struct stat *b) {
/* Returns true if the transition from a to b is safe, i.e. that we never transition from unprivileged to
* privileged files or directories. Why bother? So that unprivileged code can't symlink to privileged files

View file

@ -72,6 +72,7 @@ union inotify_event_buffer {
};
int inotify_add_watch_fd(int fd, int what, uint32_t mask);
int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask);
enum {
CHASE_PREFIX_ROOT = 1 << 0, /* The specified path will be prefixed by the specified root before beginning the iteration */

View file

@ -295,10 +295,12 @@ static int manager_check_ask_password(Manager *m) {
if (m->ask_password_inotify_fd < 0)
return log_error_errno(errno, "Failed to create inotify object: %m");
if (inotify_add_watch(m->ask_password_inotify_fd, "/run/systemd/ask-password", IN_CREATE|IN_DELETE|IN_MOVE) < 0) {
log_error_errno(errno, "Failed to watch \"/run/systemd/ask-password\": %m");
r = inotify_add_watch_and_warn(m->ask_password_inotify_fd,
"/run/systemd/ask-password",
IN_CREATE|IN_DELETE|IN_MOVE);
if (r < 0) {
manager_close_ask_password(m);
return -errno;
return r;
}
r = sd_event_add_io(m->event, &m->ask_password_event_source,

View file

@ -89,24 +89,29 @@ int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) {
break;
}
r = log_warning_errno(errno, "Failed to add watch on %s: %s", s->path, errno == ENOSPC ? "too many watches" : strerror_safe(r));
if (cut)
*cut = tmp;
goto fail;
} else {
exists = true;
/* Path exists, we don't need to watch parent too closely. */
if (oldslash) {
char *cut2 = oldslash + (oldslash == s->path);
char tmp2 = *cut2;
*cut2 = '\0';
(void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF);
/* Error is ignored, the worst can happen is we get spurious events. */
*cut2 = tmp2;
/* This second call to inotify_add_watch() should fail like the previous
* one and is done for logging the error in a comprehensive way. */
r = inotify_add_watch_and_warn(s->inotify_fd, s->path, flags);
if (r < 0) {
if (cut)
*cut = tmp;
goto fail;
}
/* Hmm, we succeeded in adding the watch this time... let's continue. */
}
exists = true;
/* Path exists, we don't need to watch parent too closely. */
if (oldslash) {
char *cut2 = oldslash + (oldslash == s->path);
char tmp2 = *cut2;
*cut2 = '\0';
(void) inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF);
/* Error is ignored, the worst can happen is we get spurious events. */
*cut2 = tmp2;
}
if (cut)

View file

@ -34,6 +34,7 @@
#include "memory-util.h"
#include "missing.h"
#include "mkdir.h"
#include "plymouth-util.h"
#include "process-util.h"
#include "random-util.h"
#include "signal-util.h"
@ -211,6 +212,186 @@ static int backspace_string(int ttyfd, const char *str) {
return backspace_chars(ttyfd, m);
}
int ask_password_plymouth(
const char *message,
usec_t until,
AskPasswordFlags flags,
const char *flag_file,
char ***ret) {
static const union sockaddr_union sa = PLYMOUTH_SOCKET;
_cleanup_close_ int fd = -1, notify = -1;
_cleanup_free_ char *packet = NULL;
ssize_t k;
int r, n;
struct pollfd pollfd[2] = {};
char buffer[LINE_MAX];
size_t p = 0;
enum {
POLL_SOCKET,
POLL_INOTIFY
};
assert(ret);
if (flag_file) {
notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
if (notify < 0)
return -errno;
r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
if (r < 0)
return -errno;
}
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return -errno;
r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
if (r < 0)
return -errno;
if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
packet = strdup("c");
n = 1;
} else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
packet = NULL;
if (!packet)
return -ENOMEM;
r = loop_write(fd, packet, n + 1, true);
if (r < 0)
return r;
pollfd[POLL_SOCKET].fd = fd;
pollfd[POLL_SOCKET].events = POLLIN;
pollfd[POLL_INOTIFY].fd = notify;
pollfd[POLL_INOTIFY].events = POLLIN;
for (;;) {
int sleep_for = -1, j;
if (until > 0) {
usec_t y;
y = now(CLOCK_MONOTONIC);
if (y > until) {
r = -ETIME;
goto finish;
}
sleep_for = (int) ((until - y) / USEC_PER_MSEC);
}
if (flag_file && access(flag_file, F_OK) < 0) {
r = -errno;
goto finish;
}
j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
if (j < 0) {
if (errno == EINTR)
continue;
r = -errno;
goto finish;
} else if (j == 0) {
r = -ETIME;
goto finish;
}
if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
(void) flush_fd(notify);
if (pollfd[POLL_SOCKET].revents == 0)
continue;
k = read(fd, buffer + p, sizeof(buffer) - p);
if (k < 0) {
if (IN_SET(errno, EINTR, EAGAIN))
continue;
r = -errno;
goto finish;
} else if (k == 0) {
r = -EIO;
goto finish;
}
p += k;
if (p < 1)
continue;
if (buffer[0] == 5) {
if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
/* Hmm, first try with cached
* passwords failed, so let's retry
* with a normal password request */
packet = mfree(packet);
if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
r = -ENOMEM;
goto finish;
}
r = loop_write(fd, packet, n+1, true);
if (r < 0)
goto finish;
flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
p = 0;
continue;
}
/* No password, because UI not shown */
r = -ENOENT;
goto finish;
} else if (IN_SET(buffer[0], 2, 9)) {
uint32_t size;
char **l;
/* One or more answers */
if (p < 5)
continue;
memcpy(&size, buffer+1, sizeof(size));
size = le32toh(size);
if (size + 5 > sizeof(buffer)) {
r = -EIO;
goto finish;
}
if (p-5 < size)
continue;
l = strv_parse_nulstr(buffer + 5, size);
if (!l) {
r = -ENOMEM;
goto finish;
}
*ret = l;
break;
} else {
/* Unknown packet */
r = -EIO;
goto finish;
}
}
r = 0;
finish:
explicit_bzero_safe(buffer, sizeof(buffer));
return r;
}
int ask_password_tty(
int ttyfd,
const char *message,
@ -371,6 +552,13 @@ int ask_password_tty(
if (n == 0 || c == '\n' || c == 0)
break;
if (c == 4) { /* C-d also known as EOT */
if (ttyfd >= 0)
(void) loop_write(ttyfd, "(skipped)", 9, false);
goto skipped;
}
if (c == 21) { /* C-u */
if (!(flags & ASK_PASSWORD_SILENT))
@ -467,6 +655,7 @@ int ask_password_tty(
if (r < 0)
goto finish;
skipped:
if (keyname)
(void) add_to_keyring_and_log(keyname, flags, l);

View file

@ -16,5 +16,6 @@ typedef enum AskPasswordFlags {
} AskPasswordFlags;
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_plymouth(const char *message, 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_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret);

View file

@ -225,9 +225,9 @@ static int run(int argc, char * argv[]) {
if (r < 0)
return log_error_errno(r, "Failed to create notify event source: %m");
r = inotify_add_watch(state.inotify_fd, "/run/systemd/", IN_CREATE);
r = inotify_add_watch_and_warn(state.inotify_fd, "/run/systemd/", IN_CREATE);
if (r < 0)
return log_error_errno(errno, "Failed to watch /run/systemd/: %m");
return r;
state.run_systemd_wd = r;

View file

@ -11,7 +11,6 @@
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/prctl.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
@ -29,6 +28,7 @@
#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
#include "hashmap.h"
#include "io-util.h"
#include "macro.h"
@ -36,7 +36,6 @@
#include "memory-util.h"
#include "mkdir.h"
#include "path-util.h"
#include "plymouth-util.h"
#include "pretty-print.h"
#include "process-util.h"
#include "set.h"
@ -58,186 +57,6 @@ static bool arg_plymouth = false;
static bool arg_console = false;
static const char *arg_device = NULL;
static int ask_password_plymouth(
const char *message,
usec_t until,
AskPasswordFlags flags,
const char *flag_file,
char ***ret) {
static const union sockaddr_union sa = PLYMOUTH_SOCKET;
_cleanup_close_ int fd = -1, notify = -1;
_cleanup_free_ char *packet = NULL;
ssize_t k;
int r, n;
struct pollfd pollfd[2] = {};
char buffer[LINE_MAX];
size_t p = 0;
enum {
POLL_SOCKET,
POLL_INOTIFY
};
assert(ret);
if (flag_file) {
notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
if (notify < 0)
return -errno;
r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
if (r < 0)
return -errno;
}
fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
if (fd < 0)
return -errno;
r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
if (r < 0)
return -errno;
if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
packet = strdup("c");
n = 1;
} else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
packet = NULL;
if (!packet)
return -ENOMEM;
r = loop_write(fd, packet, n + 1, true);
if (r < 0)
return r;
pollfd[POLL_SOCKET].fd = fd;
pollfd[POLL_SOCKET].events = POLLIN;
pollfd[POLL_INOTIFY].fd = notify;
pollfd[POLL_INOTIFY].events = POLLIN;
for (;;) {
int sleep_for = -1, j;
if (until > 0) {
usec_t y;
y = now(CLOCK_MONOTONIC);
if (y > until) {
r = -ETIME;
goto finish;
}
sleep_for = (int) ((until - y) / USEC_PER_MSEC);
}
if (flag_file && access(flag_file, F_OK) < 0) {
r = -errno;
goto finish;
}
j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
if (j < 0) {
if (errno == EINTR)
continue;
r = -errno;
goto finish;
} else if (j == 0) {
r = -ETIME;
goto finish;
}
if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
(void) flush_fd(notify);
if (pollfd[POLL_SOCKET].revents == 0)
continue;
k = read(fd, buffer + p, sizeof(buffer) - p);
if (k < 0) {
if (IN_SET(errno, EINTR, EAGAIN))
continue;
r = -errno;
goto finish;
} else if (k == 0) {
r = -EIO;
goto finish;
}
p += k;
if (p < 1)
continue;
if (buffer[0] == 5) {
if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
/* Hmm, first try with cached
* passwords failed, so let's retry
* with a normal password request */
packet = mfree(packet);
if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
r = -ENOMEM;
goto finish;
}
r = loop_write(fd, packet, n+1, true);
if (r < 0)
goto finish;
flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
p = 0;
continue;
}
/* No password, because UI not shown */
r = -ENOENT;
goto finish;
} else if (IN_SET(buffer[0], 2, 9)) {
uint32_t size;
char **l;
/* One or more answers */
if (p < 5)
continue;
memcpy(&size, buffer+1, sizeof(size));
size = le32toh(size);
if (size + 5 > sizeof(buffer)) {
r = -EIO;
goto finish;
}
if (p-5 < size)
continue;
l = strv_parse_nulstr(buffer + 5, size);
if (!l) {
r = -ENOMEM;
goto finish;
}
*ret = l;
break;
} else {
/* Unknown packet */
r = -EIO;
goto finish;
}
}
r = 0;
finish:
explicit_bzero_safe(buffer, sizeof(buffer));
return r;
}
static int send_passwords(const char *socket_name, char **passwords) {
_cleanup_(erase_and_freep) char *packet = NULL;
_cleanup_close_ int socket_fd = -1;
@ -318,7 +137,40 @@ static bool wall_tty_match(const char *path, void *userdata) {
return 0;
}
static int parse_password(const char *filename) {
static int agent_ask_password_tty(
const char *message,
usec_t until,
AskPasswordFlags flags,
const char *flag_file,
char ***ret) {
int tty_fd = -1;
int r;
if (arg_console) {
const char *con = arg_device ?: "/dev/console";
tty_fd = acquire_terminal(con, ACQUIRE_TERMINAL_WAIT, USEC_INFINITY);
if (tty_fd < 0)
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(tty_fd, message, NULL, until, flags, flag_file, ret);
if (arg_console) {
tty_fd = safe_close(tty_fd);
release_terminal();
}
return 0;
}
static int process_one_password_file(const char *filename) {
_cleanup_free_ char *socket_name = NULL, *message = NULL;
bool accept_cached = false, echo = false;
uint64_t not_after = 0;
@ -355,25 +207,28 @@ static int parse_password(const char *filename) {
if (pid > 0 && !pid_is_alive(pid))
return 0;
if (arg_action == ACTION_LIST)
switch (arg_action) {
case ACTION_LIST:
printf("'%s' (PID %u)\n", message, pid);
return 0;
else if (arg_action == ACTION_WALL) {
_cleanup_free_ char *wall = NULL;
case ACTION_WALL: {
_cleanup_free_ char *wall = NULL;
if (asprintf(&wall,
"Password entry required for \'%s\' (PID %u).\r\n"
"Please enter password with the systemd-tty-ask-password-agent tool.",
message,
pid) < 0)
return log_oom();
if (asprintf(&wall,
"Password entry required for \'%s\' (PID %u).\r\n"
"Please enter password with the systemd-tty-ask-password-agent tool.",
message,
pid) < 0)
return log_oom();
(void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL);
} else {
(void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL);
return 0;
}
case ACTION_QUERY:
case ACTION_WATCH: {
_cleanup_strv_free_erase_ char **passwords = NULL;
assert(IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH));
AskPasswordFlags flags = 0;
if (access(socket_name, W_OK) < 0) {
if (arg_action == ACTION_QUERY)
@ -382,44 +237,31 @@ static int parse_password(const char *filename) {
return 0;
}
SET_FLAG(flags, ASK_PASSWORD_ACCEPT_CACHED, accept_cached);
SET_FLAG(flags, ASK_PASSWORD_CONSOLE_COLOR, arg_console);
SET_FLAG(flags, ASK_PASSWORD_ECHO, echo);
if (arg_plymouth)
r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords);
else {
int tty_fd = -1;
r = ask_password_plymouth(message, not_after, flags, filename, &passwords);
else
r = agent_ask_password_tty(message, not_after, flags, filename, &passwords);
if (arg_console) {
const char *con = arg_device ?: "/dev/console";
if (r < 0) {
/* If the query went away, that's OK */
if (IN_SET(r, -ETIME, -ENOENT))
return 0;
tty_fd = acquire_terminal(con, ACQUIRE_TERMINAL_WAIT, USEC_INFINITY);
if (tty_fd < 0)
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(tty_fd, message, NULL, not_after,
(echo ? ASK_PASSWORD_ECHO : 0) |
(arg_console ? ASK_PASSWORD_CONSOLE_COLOR : 0),
filename, &passwords);
if (arg_console) {
tty_fd = safe_close(tty_fd);
release_terminal();
}
return log_error_errno(r, "Failed to query password: %m");
}
/* If the query went away, that's OK */
if (IN_SET(r, -ETIME, -ENOENT))
return 0;
if (r < 0)
return log_error_errno(r, "Failed to query password: %m");
if (strv_isempty(passwords))
return -ECANCELED;
r = send_passwords(socket_name, passwords);
if (r < 0)
return log_error_errno(r, "Failed to send: %m");
break;
}
}
return 0;
@ -449,7 +291,7 @@ static int wall_tty_block(void) {
return fd;
}
static int show_passwords(void) {
static int process_password_files(void) {
_cleanup_closedir_ DIR *d;
struct dirent *de;
int r = 0;
@ -462,7 +304,7 @@ static int show_passwords(void) {
return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m");
}
FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) {
FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read directory: %m")) {
_cleanup_free_ char *p = NULL;
int q;
@ -472,9 +314,6 @@ static int show_passwords(void) {
if (de->d_type != DT_REG)
continue;
if (hidden_or_backup_file(de->d_name))
continue;
if (!startswith(de->d_name, "ask."))
continue;
@ -482,7 +321,7 @@ static int show_passwords(void) {
if (!p)
return log_oom();
q = parse_password(p);
q = process_one_password_file(p);
if (q < 0 && r == 0)
r = q;
}
@ -490,10 +329,10 @@ static int show_passwords(void) {
return r;
}
static int watch_passwords(void) {
static int process_and_watch_password_files(bool watch) {
enum {
FD_INOTIFY,
FD_SIGNAL,
FD_INOTIFY,
_FD_MAX
};
@ -506,36 +345,51 @@ static int watch_passwords(void) {
(void) mkdir_p_label("/run/systemd/ask-password", 0755);
notify = inotify_init1(IN_CLOEXEC);
if (notify < 0)
return log_error_errno(errno, "Failed to allocate directory watch: %m");
if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
if (errno == ENOSPC)
return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: inotify watch limit reached");
else
return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m");
}
assert_se(sigemptyset(&mask) >= 0);
assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
assert_se(sigset_add_many(&mask, SIGTERM, -1) >= 0);
assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0);
signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
if (signal_fd < 0)
return log_error_errno(errno, "Failed to allocate signal file descriptor: %m");
if (watch) {
signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
if (signal_fd < 0)
return log_error_errno(errno, "Failed to allocate signal file descriptor: %m");
pollfd[FD_INOTIFY].fd = notify;
pollfd[FD_INOTIFY].events = POLLIN;
pollfd[FD_SIGNAL].fd = signal_fd;
pollfd[FD_SIGNAL].events = POLLIN;
pollfd[FD_SIGNAL].fd = signal_fd;
pollfd[FD_SIGNAL].events = POLLIN;
notify = inotify_init1(IN_CLOEXEC);
if (notify < 0)
return log_error_errno(errno, "Failed to allocate directory watch: %m");
r = inotify_add_watch_and_warn(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO);
if (r < 0)
return r;
pollfd[FD_INOTIFY].fd = notify;
pollfd[FD_INOTIFY].events = POLLIN;
}
for (;;) {
r = show_passwords();
if (r < 0)
log_error_errno(r, "Failed to show password: %m");
int timeout = -1;
if (poll(pollfd, _FD_MAX, -1) < 0) {
r = process_password_files();
if (r < 0) {
if (r == -ECANCELED)
/* Disable poll() timeout since at least one password has
* been skipped and therefore one file remains and is
* unlikely to trigger any events. */
timeout = 0;
else
/* FIXME: we should do something here since otherwise the service
* requesting the password won't notice the error and will wait
* indefinitely. */
log_error_errno(r, "Failed to process password: %m");
}
if (!watch)
break;
if (poll(pollfd, watch ? _FD_MAX : _FD_MAX-1, timeout) < 0) {
if (errno == EINTR)
continue;
@ -856,10 +710,7 @@ static int run(int argc, char *argv[]) {
(void) release_terminal();
}
if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
return watch_passwords();
else
return show_passwords();
return process_and_watch_password_files(arg_action != ACTION_QUERY);
}
DEFINE_MAIN_FUNCTION(run);