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) {
95 sa.sa.sa_family = AF_UNIX;
96 strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
97 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
98 log_error("Failed to connect to Plymouth: %m");
104 packet = strdup("c");
107 asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n);
114 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
115 r = k < 0 ? (int) k : -EIO;
120 pollfd[POLL_SOCKET].fd = fd;
121 pollfd[POLL_SOCKET].events = POLLIN;
122 pollfd[POLL_INOTIFY].fd = notify;
123 pollfd[POLL_INOTIFY].events = POLLIN;
126 int sleep_for = -1, j;
131 y = now(CLOCK_MONOTONIC);
138 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
142 if (access(flag_file, F_OK) < 0) {
147 if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
159 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
162 if (pollfd[POLL_SOCKET].revents == 0)
165 if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
166 r = k < 0 ? -errno : -EIO;
175 if (buffer[0] == 5) {
178 /* Hmm, first try with cached
179 * passwords failed, so let's retry
180 * with a normal password request */
184 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
189 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
190 r = k < 0 ? (int) k : -EIO;
194 accept_cached = false;
199 /* No password, because UI not shown */
203 } else if (buffer[0] == 2 || buffer[0] == 9) {
207 /* One ore more answers */
211 memcpy(&size, buffer+1, sizeof(size));
212 size = le32toh(size);
213 if (size+5 > sizeof(buffer)) {
221 if (!(l = strv_parse_nulstr(buffer + 5, size))) {
240 close_nointr_nofail(notify);
243 close_nointr_nofail(fd);
250 static int parse_password(const char *filename, char **wall) {
251 char *socket_name = NULL, *message = NULL, *packet = NULL;
252 uint64_t not_after = 0;
255 bool accept_cached = false;
257 const ConfigTableItem items[] = {
258 { "Ask", "Socket", config_parse_string, 0, &socket_name },
259 { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after },
260 { "Ask", "Message", config_parse_string, 0, &message },
261 { "Ask", "PID", config_parse_unsigned, 0, &pid },
262 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
263 { NULL, NULL, NULL, 0, NULL }
271 f = fopen(filename, "re");
276 log_error("open(%s): %m", filename);
280 r = config_parse(filename, f, NULL, config_item_table_lookup, (void*) items, true, NULL);
282 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
287 log_error("Invalid password file %s", filename);
293 if (now(CLOCK_MONOTONIC) > not_after) {
306 if (arg_action == ACTION_LIST)
307 printf("'%s' (PID %u)\n", message, pid);
308 else if (arg_action == ACTION_WALL) {
312 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
313 "Please enter password with the systemd-tty-ask-password-agent tool!",
315 *wall ? "\r\n\r\n" : "",
327 struct sockaddr_un un;
329 size_t packet_length = 0;
331 assert(arg_action == ACTION_QUERY ||
332 arg_action == ACTION_WATCH);
334 if (access(socket_name, W_OK) < 0) {
336 if (arg_action == ACTION_QUERY)
337 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
344 char **passwords = NULL;
346 if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
350 STRV_FOREACH(p, passwords)
351 packet_length += strlen(*p) + 1;
353 if (!(packet = new(char, packet_length)))
361 STRV_FOREACH(p, passwords)
362 d = stpcpy(d, *p) + 1;
371 if ((tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1)) < 0) {
376 r = ask_password_tty(message, not_after, filename, &password);
379 close_nointr_nofail(tty_fd);
384 packet_length = 1+strlen(password)+1;
385 if (!(packet = new(char, packet_length)))
389 strcpy(packet+1, password);
396 if (r == -ETIME || r == -ENOENT) {
397 /* If the query went away, that's OK */
403 log_error("Failed to query password: %s", strerror(-r));
407 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
408 log_error("socket(): %m");
414 sa.un.sun_family = AF_UNIX;
415 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
417 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
418 log_error("Failed to send: %m");
428 close_nointr_nofail(socket_fd);
437 static int wall_tty_block(void) {
442 r = get_ctty_devnr(0, &devnr);
446 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
449 mkdir_parents_label(p, 0700);
452 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
461 static bool wall_tty_match(const char *path) {
466 if (path_is_absolute(path))
467 k = lstat(path, &st);
469 if (asprintf(&p, "/dev/%s", path) < 0)
479 if (!S_ISCHR(st.st_mode))
482 /* We use named pipes to ensure that wall messages suggesting
483 * password entry are not printed over password prompts
484 * already shown. We use the fact here that opening a pipe in
485 * non-blocking mode for write-only will succeed only if
486 * there's some writer behind it. Using pipes has the
487 * advantage that the block will automatically go away if the
490 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
493 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
499 /* What, we managed to open the pipe? Then this tty is filtered. */
500 close_nointr_nofail(fd);
504 static int show_passwords(void) {
509 if (!(d = opendir("/run/systemd/ask-password"))) {
513 log_error("opendir(): %m");
517 while ((de = readdir(d))) {
522 /* We only support /dev on tmpfs, hence we can rely on
523 * d_type to be reliable */
525 if (de->d_type != DT_REG)
528 if (ignore_file(de->d_name))
531 if (!startswith(de->d_name, "ask."))
534 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
540 if ((q = parse_password(p, &wall)) < 0)
546 utmp_wall(wall, wall_tty_match);
558 static int watch_passwords(void) {
565 int notify = -1, signal_fd = -1, tty_block_fd = -1;
566 struct pollfd pollfd[_FD_MAX];
570 tty_block_fd = wall_tty_block();
572 mkdir_p_label("/run/systemd/ask-password", 0755);
574 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
579 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
584 assert_se(sigemptyset(&mask) == 0);
585 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
586 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
588 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
589 log_error("signalfd(): %m");
595 pollfd[FD_INOTIFY].fd = notify;
596 pollfd[FD_INOTIFY].events = POLLIN;
597 pollfd[FD_SIGNAL].fd = signal_fd;
598 pollfd[FD_SIGNAL].events = POLLIN;
601 if ((r = show_passwords()) < 0)
602 log_error("Failed to show password: %s", strerror(-r));
604 if (poll(pollfd, _FD_MAX, -1) < 0) {
613 if (pollfd[FD_INOTIFY].revents != 0)
616 if (pollfd[FD_SIGNAL].revents != 0)
624 close_nointr_nofail(notify);
627 close_nointr_nofail(signal_fd);
629 if (tty_block_fd >= 0)
630 close_nointr_nofail(tty_block_fd);
635 static int help(void) {
637 printf("%s [OPTIONS...]\n\n"
638 "Process system password requests.\n\n"
639 " -h --help Show this help\n"
640 " --version Show package version\n"
641 " --list Show pending password requests\n"
642 " --query Process pending password requests\n"
643 " --watch Continuously process password requests\n"
644 " --wall Continuously forward password requests to wall\n"
645 " --plymouth Ask question with Plymouth instead of on TTY\n"
646 " --console Ask question on /dev/console instead of current TTY\n",
647 program_invocation_short_name);
652 static int parse_argv(int argc, char *argv[]) {
664 static const struct option options[] = {
665 { "help", no_argument, NULL, 'h' },
666 { "version", no_argument, NULL, ARG_VERSION },
667 { "list", no_argument, NULL, ARG_LIST },
668 { "query", no_argument, NULL, ARG_QUERY },
669 { "watch", no_argument, NULL, ARG_WATCH },
670 { "wall", no_argument, NULL, ARG_WALL },
671 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
672 { "console", no_argument, NULL, ARG_CONSOLE },
681 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
690 puts(PACKAGE_STRING);
691 puts(SYSTEMD_FEATURES);
695 arg_action = ACTION_LIST;
699 arg_action = ACTION_QUERY;
703 arg_action = ACTION_WATCH;
707 arg_action = ACTION_WALL;
722 log_error("Unknown option code %c", c);
727 if (optind != argc) {
735 int main(int argc, char *argv[]) {
738 log_set_target(LOG_TARGET_AUTO);
739 log_parse_environment();
744 if ((r = parse_argv(argc, argv)) <= 0)
752 if (arg_action == ACTION_WATCH ||
753 arg_action == ACTION_WALL)
754 r = watch_passwords();
756 r = show_passwords();
759 log_error("Error: %s", strerror(-r));
762 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;