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");
105 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1),
114 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
115 r = k < 0 ? (int) k : -EIO;
119 pollfd[POLL_SOCKET].fd = fd;
120 pollfd[POLL_SOCKET].events = POLLIN;
121 pollfd[POLL_INOTIFY].fd = notify;
122 pollfd[POLL_INOTIFY].events = POLLIN;
125 int sleep_for = -1, j;
130 y = now(CLOCK_MONOTONIC);
137 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
141 if (access(flag_file, F_OK) < 0) {
146 if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
158 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
161 if (pollfd[POLL_SOCKET].revents == 0)
164 if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
165 r = k < 0 ? -errno : -EIO;
174 if (buffer[0] == 5) {
177 /* Hmm, first try with cached
178 * passwords failed, so let's retry
179 * with a normal password request */
183 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
188 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
189 r = k < 0 ? (int) k : -EIO;
193 accept_cached = false;
198 /* No password, because UI not shown */
202 } else if (buffer[0] == 2 || buffer[0] == 9) {
206 /* One ore more answers */
210 memcpy(&size, buffer+1, sizeof(size));
211 size = le32toh(size);
212 if (size+5 > sizeof(buffer)) {
220 if (!(l = strv_parse_nulstr(buffer + 5, size))) {
246 static int parse_password(const char *filename, char **wall) {
247 _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL;
248 uint64_t not_after = 0;
250 bool accept_cached = false;
252 const ConfigTableItem items[] = {
253 { "Ask", "Socket", config_parse_string, 0, &socket_name },
254 { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after },
255 { "Ask", "Message", config_parse_string, 0, &message },
256 { "Ask", "PID", config_parse_unsigned, 0, &pid },
257 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
258 { NULL, NULL, NULL, 0, NULL }
265 r = config_parse(NULL, filename, NULL,
267 config_item_table_lookup, items,
268 true, false, true, NULL);
273 log_error("Invalid password file %s", filename);
277 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
280 if (pid > 0 && !pid_is_alive(pid))
283 if (arg_action == ACTION_LIST)
284 printf("'%s' (PID %u)\n", message, pid);
286 else if (arg_action == ACTION_WALL) {
290 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
291 "Please enter password with the systemd-tty-ask-password-agent tool!",
293 *wall ? "\r\n\r\n" : "",
302 union sockaddr_union sa = {};
303 size_t packet_length = 0;
304 _cleanup_close_ int socket_fd = -1;
306 assert(arg_action == ACTION_QUERY ||
307 arg_action == ACTION_WATCH);
309 if (access(socket_name, W_OK) < 0) {
310 if (arg_action == ACTION_QUERY)
311 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
317 _cleanup_strv_free_ char **passwords = NULL;
319 r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
324 STRV_FOREACH(p, passwords)
325 packet_length += strlen(*p) + 1;
327 packet = new(char, packet_length);
336 STRV_FOREACH(p, passwords)
337 d = stpcpy(d, *p) + 1;
343 _cleanup_free_ char *password = NULL;
346 tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
351 r = ask_password_tty(message, not_after, filename, &password);
359 packet_length = 1 + strlen(password) + 1;
360 packet = new(char, packet_length);
365 strcpy(packet + 1, password);
370 if (r == -ETIME || r == -ENOENT)
371 /* If the query went away, that's OK */
375 log_error("Failed to query password: %s", strerror(-r));
379 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
381 log_error("socket(): %m");
385 sa.un.sun_family = AF_UNIX;
386 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
388 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
389 offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
391 log_error("Failed to send: %m");
399 static int wall_tty_block(void) {
404 r = get_ctty_devnr(0, &devnr);
408 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
411 mkdir_parents_label(p, 0700);
414 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
423 static bool wall_tty_match(const char *path) {
428 if (path_is_absolute(path))
429 k = lstat(path, &st);
431 if (asprintf(&p, "/dev/%s", path) < 0)
441 if (!S_ISCHR(st.st_mode))
444 /* We use named pipes to ensure that wall messages suggesting
445 * password entry are not printed over password prompts
446 * already shown. We use the fact here that opening a pipe in
447 * non-blocking mode for write-only will succeed only if
448 * there's some writer behind it. Using pipes has the
449 * advantage that the block will automatically go away if the
452 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
455 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
461 /* What, we managed to open the pipe? Then this tty is filtered. */
466 static int show_passwords(void) {
471 if (!(d = opendir("/run/systemd/ask-password"))) {
475 log_error("opendir(/run/systemd/ask-password): %m");
479 while ((de = readdir(d))) {
484 /* We only support /dev on tmpfs, hence we can rely on
485 * d_type to be reliable */
487 if (de->d_type != DT_REG)
490 if (ignore_file(de->d_name))
493 if (!startswith(de->d_name, "ask."))
496 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
502 if ((q = parse_password(p, &wall)) < 0)
508 utmp_wall(wall, NULL, wall_tty_match);
520 static int watch_passwords(void) {
527 int notify = -1, signal_fd = -1, tty_block_fd = -1;
528 struct pollfd pollfd[_FD_MAX] = {};
532 tty_block_fd = wall_tty_block();
534 mkdir_p_label("/run/systemd/ask-password", 0755);
536 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
541 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
546 assert_se(sigemptyset(&mask) == 0);
547 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
548 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
550 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
551 log_error("signalfd(): %m");
556 pollfd[FD_INOTIFY].fd = notify;
557 pollfd[FD_INOTIFY].events = POLLIN;
558 pollfd[FD_SIGNAL].fd = signal_fd;
559 pollfd[FD_SIGNAL].events = POLLIN;
562 if ((r = show_passwords()) < 0)
563 log_error("Failed to show password: %s", strerror(-r));
565 if (poll(pollfd, _FD_MAX, -1) < 0) {
574 if (pollfd[FD_INOTIFY].revents != 0)
577 if (pollfd[FD_SIGNAL].revents != 0)
585 safe_close(signal_fd);
586 safe_close(tty_block_fd);
591 static int help(void) {
593 printf("%s [OPTIONS...]\n\n"
594 "Process system password requests.\n\n"
595 " -h --help Show this help\n"
596 " --version Show package version\n"
597 " --list Show pending password requests\n"
598 " --query Process pending password requests\n"
599 " --watch Continuously process password requests\n"
600 " --wall Continuously forward password requests to wall\n"
601 " --plymouth Ask question with Plymouth instead of on TTY\n"
602 " --console Ask question on /dev/console instead of current TTY\n",
603 program_invocation_short_name);
608 static int parse_argv(int argc, char *argv[]) {
620 static const struct option options[] = {
621 { "help", no_argument, NULL, 'h' },
622 { "version", no_argument, NULL, ARG_VERSION },
623 { "list", no_argument, NULL, ARG_LIST },
624 { "query", no_argument, NULL, ARG_QUERY },
625 { "watch", no_argument, NULL, ARG_WATCH },
626 { "wall", no_argument, NULL, ARG_WALL },
627 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
628 { "console", no_argument, NULL, ARG_CONSOLE },
637 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
645 puts(PACKAGE_STRING);
646 puts(SYSTEMD_FEATURES);
650 arg_action = ACTION_LIST;
654 arg_action = ACTION_QUERY;
658 arg_action = ACTION_WATCH;
662 arg_action = ACTION_WALL;
677 assert_not_reached("Unhandled option");
681 if (optind != argc) {
689 int main(int argc, char *argv[]) {
692 log_set_target(LOG_TARGET_AUTO);
693 log_parse_environment();
698 if ((r = parse_argv(argc, argv)) <= 0)
706 if (arg_action == ACTION_WATCH ||
707 arg_action == ACTION_WALL)
708 r = watch_passwords();
710 r = show_passwords();
713 log_error("Error: %s", strerror(-r));
716 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;