1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2010 Lennart Poettering
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
25 #include <sys/socket.h>
29 #include <sys/inotify.h>
32 #include <sys/signalfd.h>
37 #include "path-util.h"
38 #include "conf-parser.h"
39 #include "utmp-wtmp.h"
40 #include "socket-util.h"
41 #include "ask-password-api.h"
50 } arg_action = ACTION_QUERY;
52 static bool arg_plymouth = false;
53 static bool arg_console = false;
55 static int ask_password_plymouth(
58 const char *flag_file,
60 char ***_passphrases) {
62 int fd = -1, notify = -1;
63 union sockaddr_union sa = {};
67 struct pollfd pollfd[2] = {};
68 char buffer[LINE_MAX];
78 if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
83 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
89 if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
94 sa.sa.sa_family = AF_UNIX;
95 strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
96 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
97 log_error("Failed to connect to Plymouth: %m");
103 packet = strdup("c");
106 asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n);
113 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
114 r = k < 0 ? (int) k : -EIO;
118 pollfd[POLL_SOCKET].fd = fd;
119 pollfd[POLL_SOCKET].events = POLLIN;
120 pollfd[POLL_INOTIFY].fd = notify;
121 pollfd[POLL_INOTIFY].events = POLLIN;
124 int sleep_for = -1, j;
129 y = now(CLOCK_MONOTONIC);
136 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
140 if (access(flag_file, F_OK) < 0) {
145 if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
157 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
160 if (pollfd[POLL_SOCKET].revents == 0)
163 if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
164 r = k < 0 ? -errno : -EIO;
173 if (buffer[0] == 5) {
176 /* Hmm, first try with cached
177 * passwords failed, so let's retry
178 * with a normal password request */
182 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
187 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
188 r = k < 0 ? (int) k : -EIO;
192 accept_cached = false;
197 /* No password, because UI not shown */
201 } else if (buffer[0] == 2 || buffer[0] == 9) {
205 /* One ore more answers */
209 memcpy(&size, buffer+1, sizeof(size));
210 size = le32toh(size);
211 if (size+5 > sizeof(buffer)) {
219 if (!(l = strv_parse_nulstr(buffer + 5, size))) {
238 close_nointr_nofail(notify);
241 close_nointr_nofail(fd);
248 static int parse_password(const char *filename, char **wall) {
249 char *socket_name = NULL, *message = NULL, *packet = NULL;
250 uint64_t not_after = 0;
253 bool accept_cached = false;
255 const ConfigTableItem items[] = {
256 { "Ask", "Socket", config_parse_string, 0, &socket_name },
257 { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after },
258 { "Ask", "Message", config_parse_string, 0, &message },
259 { "Ask", "PID", config_parse_unsigned, 0, &pid },
260 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
261 { NULL, NULL, NULL, 0, NULL }
269 f = fopen(filename, "re");
274 log_error("open(%s): %m", filename);
278 r = config_parse(NULL, filename, f, NULL, config_item_table_lookup, (void*) items, true, false, NULL);
280 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
285 log_error("Invalid password file %s", filename);
291 if (now(CLOCK_MONOTONIC) > not_after) {
304 if (arg_action == ACTION_LIST)
305 printf("'%s' (PID %u)\n", message, pid);
306 else if (arg_action == ACTION_WALL) {
310 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
311 "Please enter password with the systemd-tty-ask-password-agent tool!",
313 *wall ? "\r\n\r\n" : "",
325 struct sockaddr_un un;
327 size_t packet_length = 0;
329 assert(arg_action == ACTION_QUERY ||
330 arg_action == ACTION_WATCH);
332 if (access(socket_name, W_OK) < 0) {
334 if (arg_action == ACTION_QUERY)
335 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
342 _cleanup_strv_free_ char **passwords = NULL;
344 if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
348 STRV_FOREACH(p, passwords)
349 packet_length += strlen(*p) + 1;
351 if (!(packet = new(char, packet_length)))
359 STRV_FOREACH(p, passwords)
360 d = stpcpy(d, *p) + 1;
369 if ((tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1)) < 0) {
374 r = ask_password_tty(message, not_after, filename, &password);
377 close_nointr_nofail(tty_fd);
382 packet_length = 1+strlen(password)+1;
383 if (!(packet = new(char, packet_length)))
387 strcpy(packet+1, password);
394 if (r == -ETIME || r == -ENOENT) {
395 /* If the query went away, that's OK */
401 log_error("Failed to query password: %s", strerror(-r));
405 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
406 log_error("socket(): %m");
411 sa.un.sun_family = AF_UNIX;
412 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
414 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
415 log_error("Failed to send: %m");
425 close_nointr_nofail(socket_fd);
434 static int wall_tty_block(void) {
439 r = get_ctty_devnr(0, &devnr);
443 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
446 mkdir_parents_label(p, 0700);
449 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
458 static bool wall_tty_match(const char *path) {
463 if (path_is_absolute(path))
464 k = lstat(path, &st);
466 if (asprintf(&p, "/dev/%s", path) < 0)
476 if (!S_ISCHR(st.st_mode))
479 /* We use named pipes to ensure that wall messages suggesting
480 * password entry are not printed over password prompts
481 * already shown. We use the fact here that opening a pipe in
482 * non-blocking mode for write-only will succeed only if
483 * there's some writer behind it. Using pipes has the
484 * advantage that the block will automatically go away if the
487 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
490 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
496 /* What, we managed to open the pipe? Then this tty is filtered. */
497 close_nointr_nofail(fd);
501 static int show_passwords(void) {
506 if (!(d = opendir("/run/systemd/ask-password"))) {
510 log_error("opendir(): %m");
514 while ((de = readdir(d))) {
519 /* We only support /dev on tmpfs, hence we can rely on
520 * d_type to be reliable */
522 if (de->d_type != DT_REG)
525 if (ignore_file(de->d_name))
528 if (!startswith(de->d_name, "ask."))
531 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
537 if ((q = parse_password(p, &wall)) < 0)
543 utmp_wall(wall, wall_tty_match);
555 static int watch_passwords(void) {
562 int notify = -1, signal_fd = -1, tty_block_fd = -1;
563 struct pollfd pollfd[_FD_MAX] = {};
567 tty_block_fd = wall_tty_block();
569 mkdir_p_label("/run/systemd/ask-password", 0755);
571 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
576 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
581 assert_se(sigemptyset(&mask) == 0);
582 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
583 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
585 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
586 log_error("signalfd(): %m");
591 pollfd[FD_INOTIFY].fd = notify;
592 pollfd[FD_INOTIFY].events = POLLIN;
593 pollfd[FD_SIGNAL].fd = signal_fd;
594 pollfd[FD_SIGNAL].events = POLLIN;
597 if ((r = show_passwords()) < 0)
598 log_error("Failed to show password: %s", strerror(-r));
600 if (poll(pollfd, _FD_MAX, -1) < 0) {
609 if (pollfd[FD_INOTIFY].revents != 0)
612 if (pollfd[FD_SIGNAL].revents != 0)
620 close_nointr_nofail(notify);
623 close_nointr_nofail(signal_fd);
625 if (tty_block_fd >= 0)
626 close_nointr_nofail(tty_block_fd);
631 static int help(void) {
633 printf("%s [OPTIONS...]\n\n"
634 "Process system password requests.\n\n"
635 " -h --help Show this help\n"
636 " --version Show package version\n"
637 " --list Show pending password requests\n"
638 " --query Process pending password requests\n"
639 " --watch Continuously process password requests\n"
640 " --wall Continuously forward password requests to wall\n"
641 " --plymouth Ask question with Plymouth instead of on TTY\n"
642 " --console Ask question on /dev/console instead of current TTY\n",
643 program_invocation_short_name);
648 static int parse_argv(int argc, char *argv[]) {
660 static const struct option options[] = {
661 { "help", no_argument, NULL, 'h' },
662 { "version", no_argument, NULL, ARG_VERSION },
663 { "list", no_argument, NULL, ARG_LIST },
664 { "query", no_argument, NULL, ARG_QUERY },
665 { "watch", no_argument, NULL, ARG_WATCH },
666 { "wall", no_argument, NULL, ARG_WALL },
667 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
668 { "console", no_argument, NULL, ARG_CONSOLE },
677 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
686 puts(PACKAGE_STRING);
687 puts(SYSTEMD_FEATURES);
691 arg_action = ACTION_LIST;
695 arg_action = ACTION_QUERY;
699 arg_action = ACTION_WATCH;
703 arg_action = ACTION_WALL;
718 log_error("Unknown option code %c", c);
723 if (optind != argc) {
731 int main(int argc, char *argv[]) {
734 log_set_target(LOG_TARGET_AUTO);
735 log_parse_environment();
740 if ((r = parse_argv(argc, argv)) <= 0)
748 if (arg_action == ACTION_WATCH ||
749 arg_action == ACTION_WALL)
750 r = watch_passwords();
752 r = show_passwords();
755 log_error("Error: %s", strerror(-r));
758 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;