X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Ftty-ask-password-agent.c;h=13481b29e950f337c726cce28dd184aba1acde93;hp=8b02b269592d67d68e45d020023a3e057440cde3;hb=731a676c206bb1b48e7f736ce9f5b730252d1601;hpb=656ce8f77a840b7615180de4d63c4fc29b40cc47 diff --git a/src/tty-ask-password-agent.c b/src/tty-ask-password-agent.c index 8b02b2695..13481b29e 100644 --- a/src/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent.c @@ -30,12 +30,14 @@ #include #include #include +#include #include "util.h" #include "conf-parser.h" #include "utmp-wtmp.h" #include "socket-util.h" #include "ask-password-api.h" +#include "strv.h" static enum { ACTION_LIST, @@ -45,8 +47,15 @@ static enum { } arg_action = ACTION_QUERY; static bool arg_plymouth = false; +static bool arg_console = false; + +static int ask_password_plymouth( + const char *message, + usec_t until, + const char *flag_file, + bool accept_cached, + char ***_passphrases) { -static int ask_password_plymouth(const char *message, usec_t until, const char *flag_file, char **_passphrase) { int fd = -1, notify = -1; union sockaddr_union sa; char *packet = NULL; @@ -60,6 +69,8 @@ static int ask_password_plymouth(const char *message, usec_t until, const char * POLL_INOTIFY }; + assert(_passphrases); + if (flag_file) { if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) { r = -errno; @@ -79,14 +90,20 @@ static int ask_password_plymouth(const char *message, usec_t until, const char * zero(sa); sa.sa.sa_family = AF_UNIX; - strncpy(sa.un.sun_path+1, "/ply-boot-protocol", sizeof(sa.un.sun_path)-1); - - if (connect(fd, &sa.sa, sizeof(sa.un)) < 0) { + strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1); + if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { + log_error("Failed to connect to Plymouth: %m"); r = -errno; goto finish; } - if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { + if (accept_cached) { + packet = strdup("c"); + n = 1; + } else + asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n); + + if (!packet) { r = -ENOMEM; goto finish; } @@ -111,7 +128,7 @@ static int ask_password_plymouth(const char *message, usec_t until, const char * y = now(CLOCK_MONOTONIC); if (y > until) { - r = -ETIMEDOUT; + r = -ETIME; goto finish; } @@ -132,7 +149,7 @@ static int ask_password_plymouth(const char *message, usec_t until, const char * r = -errno; goto finish; } else if (j == 0) { - r = -ETIMEDOUT; + r = -ETIME; goto finish; } @@ -153,19 +170,43 @@ static int ask_password_plymouth(const char *message, usec_t until, const char * continue; if (buffer[0] == 5) { + + if (accept_cached) { + /* Hmm, first try with cached + * passwords failed, so let's retry + * with a normal password request */ + free(packet); + packet = NULL; + + if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { + r = -ENOMEM; + goto finish; + } + + if ((k = loop_write(fd, packet, n+1, true)) != n+1) { + r = k < 0 ? (int) k : -EIO; + goto finish; + } + + accept_cached = false; + p = 0; + continue; + } + /* No password, because UI not shown */ r = -ENOENT; goto finish; - } else if (buffer[0] == 2) { + } else if (buffer[0] == 2 || buffer[0] == 9) { uint32_t size; - char *s; + char **l; - /* One answer */ + /* One ore more answers */ if (p < 5) continue; memcpy(&size, buffer+1, sizeof(size)); + size = le32toh(size); if (size+5 > sizeof(buffer)) { r = -EIO; goto finish; @@ -174,13 +215,14 @@ static int ask_password_plymouth(const char *message, usec_t until, const char * if (p-5 < size) continue; - if (!(s = strndup(buffer + 5, size))) { + if (!(l = strv_parse_nulstr(buffer + 5, size))) { r = -ENOMEM; goto finish; } - *_passphrase = s; + *_passphrases = l; break; + } else { /* Unknown packet */ r = -EIO; @@ -207,23 +249,24 @@ static int parse_password(const char *filename, char **wall) { uint64_t not_after = 0; unsigned pid = 0; int socket_fd = -1; - - const ConfigItem items[] = { - { "Socket", config_parse_string, &socket_name, "Ask" }, - { "NotAfter", config_parse_uint64, ¬_after, "Ask" }, - { "Message", config_parse_string, &message, "Ask" }, - { "PID", config_parse_unsigned, &pid, "Ask" }, - { NULL, NULL, NULL, NULL } + bool accept_cached = false; + + const ConfigTableItem items[] = { + { "Ask", "Socket", config_parse_string, 0, &socket_name }, + { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after }, + { "Ask", "Message", config_parse_string, 0, &message }, + { "Ask", "PID", config_parse_unsigned, 0, &pid }, + { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached }, + { NULL, NULL, NULL, 0, NULL } }; FILE *f; int r; - usec_t n; assert(filename); - if (!(f = fopen(filename, "re"))) { - + f = fopen(filename, "re"); + if (!f) { if (errno == ENOENT) return 0; @@ -231,19 +274,28 @@ static int parse_password(const char *filename, char **wall) { return -errno; } - if ((r = config_parse(filename, f, NULL, items, true, NULL)) < 0) { + r = config_parse(filename, f, NULL, config_item_table_lookup, (void*) items, true, NULL); + if (r < 0) { log_error("Failed to parse password file %s: %s", filename, strerror(-r)); goto finish; } - if (!socket_name || not_after <= 0) { + if (!socket_name) { log_error("Invalid password file %s", filename); r = -EBADMSG; goto finish; } - n = now(CLOCK_MONOTONIC); - if (n > not_after) { + if (not_after > 0) { + if (now(CLOCK_MONOTONIC) > not_after) { + r = 0; + goto finish; + } + } + + if (pid > 0 && + kill(pid, 0) < 0 && + errno == ESRCH) { r = 0; goto finish; } @@ -255,7 +307,7 @@ static int parse_password(const char *filename, char **wall) { if (asprintf(&_wall, "%s%sPassword entry required for \'%s\' (PID %u).\r\n" - "Please enter password with the systemd-tty-password-agent tool!", + "Please enter password with the systemd-tty-ask-password-agent tool!", *wall ? *wall : "", *wall ? "\r\n\r\n" : "", message, @@ -272,7 +324,7 @@ static int parse_password(const char *filename, char **wall) { struct sockaddr sa; struct sockaddr_un un; } sa; - char *password; + size_t packet_length = 0; assert(arg_action == ACTION_QUERY || arg_action == ACTION_WATCH); @@ -286,22 +338,67 @@ static int parse_password(const char *filename, char **wall) { goto finish; } - if (arg_plymouth) - r = ask_password_plymouth(message, not_after, filename, &password); - else + if (arg_plymouth) { + char **passwords = NULL; + + if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) { + char **p; + + packet_length = 1; + STRV_FOREACH(p, passwords) + packet_length += strlen(*p) + 1; + + if (!(packet = new(char, packet_length))) + r = -ENOMEM; + else { + char *d; + + packet[0] = '+'; + d = packet+1; + + STRV_FOREACH(p, passwords) + d = stpcpy(d, *p) + 1; + } + } + + } else { + int tty_fd = -1; + char *password; + + if (arg_console) + if ((tty_fd = acquire_terminal("/dev/console", false, false, false)) < 0) { + r = tty_fd; + goto finish; + } + r = ask_password_tty(message, not_after, filename, &password); - if (r < 0) { - log_error("Failed to query password: %s", strerror(-r)); - goto finish; + if (arg_console) { + close_nointr_nofail(tty_fd); + release_terminal(); + } + + if (r >= 0) { + packet_length = 1+strlen(password)+1; + if (!(packet = new(char, packet_length))) + r = -ENOMEM; + else { + packet[0] = '+'; + strcpy(packet+1, password); + } + + free(password); + } } - asprintf(&packet, "+%s", password); - free(password); + if (r == -ETIME || r == -ENOENT) { + /* If the query went away, that's OK */ + r = 0; + goto finish; + } - if (!packet) { - log_error("Out of memory"); - r = -ENOMEM; + if (r < 0) { + log_error("Failed to query password: %s", strerror(-r)); goto finish; } @@ -315,7 +412,7 @@ static int parse_password(const char *filename, char **wall) { sa.un.sun_family = AF_UNIX; strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); - if (sendto(socket_fd, packet, strlen(packet), MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) { + if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) { log_error("Failed to send: %m"); r = -errno; goto finish; @@ -335,12 +432,79 @@ finish: return r; } +static int wall_tty_block(void) { + char *p; + int fd, r; + dev_t devnr; + + r = get_ctty_devnr(0, &devnr); + if (r < 0) + return -r; + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0) + return -ENOMEM; + + mkdir_parents(p, 0700); + mkfifo(p, 0600); + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + free(p); + + if (fd < 0) + return -errno; + + return fd; +} + +static bool wall_tty_match(const char *path) { + int fd, k; + char *p; + struct stat st; + + if (path_is_absolute(path)) + k = lstat(path, &st); + else { + if (asprintf(&p, "/dev/%s", path) < 0) + return true; + + k = lstat(p, &st); + free(p); + } + + if (k < 0) + return true; + + if (!S_ISCHR(st.st_mode)) + return true; + + /* We use named pipes to ensure that wall messages suggesting + * password entry are not printed over password prompts + * already shown. We use the fact here that opening a pipe in + * non-blocking mode for write-only will succeed only if + * there's some writer behind it. Using pipes has the + * advantage that the block will automatically go away if the + * process dies. */ + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) + return true; + + fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + free(p); + + if (fd < 0) + return true; + + /* What, we managed to open the pipe? Then this tty is filtered. */ + close_nointr_nofail(fd); + return false; +} + static int show_passwords(void) { DIR *d; struct dirent *de; int r = 0; - if (!(d = opendir("/dev/.systemd/ask-password"))) { + if (!(d = opendir("/run/systemd/ask-password"))) { if (errno == ENOENT) return 0; @@ -353,6 +517,9 @@ static int show_passwords(void) { int q; char *wall; + /* We only support /dev on tmpfs, hence we can rely on + * d_type to be reliable */ + if (de->d_type != DT_REG) continue; @@ -362,7 +529,7 @@ static int show_passwords(void) { if (!startswith(de->d_name, "ask.")) continue; - if (!(p = strappend("/dev/.systemd/ask-password/", de->d_name))) { + if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) { log_error("Out of memory"); r = -ENOMEM; goto finish; @@ -375,7 +542,7 @@ static int show_passwords(void) { free(p); if (wall) { - utmp_wall(wall); + utmp_wall(wall, wall_tty_match); free(wall); } } @@ -394,19 +561,21 @@ static int watch_passwords(void) { _FD_MAX }; - int notify = -1, signal_fd = -1; + int notify = -1, signal_fd = -1, tty_block_fd = -1; struct pollfd pollfd[_FD_MAX]; sigset_t mask; int r; - mkdir_p("/dev/.systemd/ask-password", 0755); + tty_block_fd = wall_tty_block(); + + mkdir_p("/run/systemd/ask-password", 0755); if ((notify = inotify_init1(IN_CLOEXEC)) < 0) { r = -errno; goto finish; } - if (inotify_add_watch(notify, "/dev/.systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) { + if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) { r = -errno; goto finish; } @@ -429,7 +598,7 @@ static int watch_passwords(void) { for (;;) { if ((r = show_passwords()) < 0) - break; + log_error("Failed to show password: %s", strerror(-r)); if (poll(pollfd, _FD_MAX, -1) < 0) { @@ -456,6 +625,9 @@ finish: if (signal_fd >= 0) close_nointr_nofail(signal_fd); + if (tty_block_fd >= 0) + close_nointr_nofail(tty_block_fd); + return r; } @@ -466,9 +638,10 @@ static int help(void) { " -h --help Show this help\n" " --list Show pending password requests\n" " --query Process pending password requests\n" - " --watch Continously process password requests\n" - " --wall Continously forward password requests to wall\n" - " --plymouth Ask question with Plymouth instead of on TTY\n", + " --watch Continuously process password requests\n" + " --wall Continuously forward password requests to wall\n" + " --plymouth Ask question with Plymouth instead of on TTY\n" + " --console Ask question on /dev/console instead of current TTY\n", program_invocation_short_name); return 0; @@ -481,7 +654,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_QUERY, ARG_WATCH, ARG_WALL, - ARG_PLYMOUTH + ARG_PLYMOUTH, + ARG_CONSOLE }; static const struct option options[] = { @@ -491,6 +665,7 @@ static int parse_argv(int argc, char *argv[]) { { "watch", no_argument, NULL, ARG_WATCH }, { "wall", no_argument, NULL, ARG_WALL }, { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, + { "console", no_argument, NULL, ARG_CONSOLE }, { NULL, 0, NULL, 0 } }; @@ -527,6 +702,10 @@ static int parse_argv(int argc, char *argv[]) { arg_plymouth = true; break; + case ARG_CONSOLE: + arg_console = true; + break; + case '?': return -EINVAL; @@ -550,15 +729,25 @@ int main(int argc, char *argv[]) { log_parse_environment(); log_open(); + umask(0022); + if ((r = parse_argv(argc, argv)) <= 0) goto finish; + if (arg_console) { + setsid(); + release_terminal(); + } + if (arg_action == ACTION_WATCH || arg_action == ACTION_WALL) r = watch_passwords(); else r = show_passwords(); + if (r < 0) + log_error("Error: %s", strerror(-r)); + finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; }