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"
51 } arg_action = ACTION_QUERY;
53 static bool arg_plymouth = false;
54 static bool arg_console = false;
56 static int ask_password_plymouth(
59 const char *flag_file,
61 char ***_passphrases) {
63 _cleanup_close_ int fd = -1, notify = -1;
64 union sockaddr_union sa = PLYMOUTH_SOCKET;
65 _cleanup_free_ char *packet = NULL;
68 struct pollfd pollfd[2] = {};
69 char buffer[LINE_MAX];
79 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
83 r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
88 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
92 r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1));
94 log_error("Failed to connect to Plymouth: %m");
101 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1),
108 k = loop_write(fd, packet, n + 1, true);
110 return k < 0 ? (int) k : -EIO;
112 pollfd[POLL_SOCKET].fd = fd;
113 pollfd[POLL_SOCKET].events = POLLIN;
114 pollfd[POLL_INOTIFY].fd = notify;
115 pollfd[POLL_INOTIFY].events = POLLIN;
118 int sleep_for = -1, j;
123 y = now(CLOCK_MONOTONIC);
128 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
131 if (flag_file && access(flag_file, F_OK) < 0)
134 j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
143 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
146 if (pollfd[POLL_SOCKET].revents == 0)
149 k = read(fd, buffer + p, sizeof(buffer) - p);
151 return r = k < 0 ? -errno : -EIO;
158 if (buffer[0] == 5) {
161 /* Hmm, first try with cached
162 * passwords failed, so let's retry
163 * with a normal password request */
167 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
170 k = loop_write(fd, packet, n+1, true);
172 return k < 0 ? (int) k : -EIO;
174 accept_cached = false;
179 /* No password, because UI not shown */
182 } else if (buffer[0] == 2 || buffer[0] == 9) {
186 /* One ore more answers */
190 memcpy(&size, buffer+1, sizeof(size));
191 size = le32toh(size);
192 if (size + 5 > sizeof(buffer))
198 l = strv_parse_nulstr(buffer + 5, size);
213 static int parse_password(const char *filename, char **wall) {
214 _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL;
215 uint64_t not_after = 0;
217 bool accept_cached = false;
219 const ConfigTableItem items[] = {
220 { "Ask", "Socket", config_parse_string, 0, &socket_name },
221 { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after },
222 { "Ask", "Message", config_parse_string, 0, &message },
223 { "Ask", "PID", config_parse_unsigned, 0, &pid },
224 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
232 r = config_parse(NULL, filename, NULL,
234 config_item_table_lookup, items,
235 true, false, true, NULL);
240 log_error("Invalid password file %s", filename);
244 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
247 if (pid > 0 && !pid_is_alive(pid))
250 if (arg_action == ACTION_LIST)
251 printf("'%s' (PID %u)\n", message, pid);
253 else if (arg_action == ACTION_WALL) {
257 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
258 "Please enter password with the systemd-tty-ask-password-agent tool!",
260 *wall ? "\r\n\r\n" : "",
269 union sockaddr_union sa = {};
270 size_t packet_length = 0;
271 _cleanup_close_ int socket_fd = -1;
273 assert(arg_action == ACTION_QUERY ||
274 arg_action == ACTION_WATCH);
276 if (access(socket_name, W_OK) < 0) {
277 if (arg_action == ACTION_QUERY)
278 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
284 _cleanup_strv_free_ char **passwords = NULL;
286 r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
291 STRV_FOREACH(p, passwords)
292 packet_length += strlen(*p) + 1;
294 packet = new(char, packet_length);
298 char *d = packet + 1;
300 STRV_FOREACH(p, passwords)
301 d = stpcpy(d, *p) + 1;
309 _cleanup_free_ char *password = NULL;
312 tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
317 r = ask_password_tty(message, not_after, filename, &password);
325 packet_length = 1 + strlen(password) + 1;
326 packet = new(char, packet_length);
331 strcpy(packet + 1, password);
336 if (IN_SET(r, -ETIME, -ENOENT))
337 /* If the query went away, that's OK */
341 log_error("Failed to query password: %s", strerror(-r));
345 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
347 log_error("socket(): %m");
351 sa.un.sun_family = AF_UNIX;
352 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
354 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
355 offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
357 log_error("Failed to send: %m");
365 static int wall_tty_block(void) {
366 _cleanup_free_ char *p = NULL;
370 r = get_ctty_devnr(0, &devnr);
374 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
377 mkdir_parents_label(p, 0700);
380 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
387 static bool wall_tty_match(const char *path) {
390 _cleanup_free_ char *p = NULL;
392 if (!path_is_absolute(path))
393 path = strappenda("/dev/", path);
395 r = lstat(path, &st);
399 if (!S_ISCHR(st.st_mode))
402 /* We use named pipes to ensure that wall messages suggesting
403 * password entry are not printed over password prompts
404 * already shown. We use the fact here that opening a pipe in
405 * non-blocking mode for write-only will succeed only if
406 * there's some writer behind it. Using pipes has the
407 * advantage that the block will automatically go away if the
410 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
413 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
417 /* What, we managed to open the pipe? Then this tty is filtered. */
422 static int show_passwords(void) {
423 _cleanup_closedir_ DIR *d;
427 d = opendir("/run/systemd/ask-password");
432 log_error("opendir(/run/systemd/ask-password): %m");
436 while ((de = readdir(d))) {
437 _cleanup_free_ char *p = NULL, *wall = NULL;
440 /* We only support /dev on tmpfs, hence we can rely on
441 * d_type to be reliable */
443 if (de->d_type != DT_REG)
446 if (ignore_file(de->d_name))
449 if (!startswith(de->d_name, "ask."))
452 p = strappend("/run/systemd/ask-password/", de->d_name);
456 q = parse_password(p, &wall);
461 utmp_wall(wall, NULL, wall_tty_match);
467 static int watch_passwords(void) {
474 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
475 struct pollfd pollfd[_FD_MAX] = {};
479 tty_block_fd = wall_tty_block();
481 mkdir_p_label("/run/systemd/ask-password", 0755);
483 notify = inotify_init1(IN_CLOEXEC);
487 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
490 assert_se(sigemptyset(&mask) == 0);
491 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
492 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
494 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
498 pollfd[FD_INOTIFY].fd = notify;
499 pollfd[FD_INOTIFY].events = POLLIN;
500 pollfd[FD_SIGNAL].fd = signal_fd;
501 pollfd[FD_SIGNAL].events = POLLIN;
504 r = show_passwords();
506 log_error("Failed to show password: %s", strerror(-r));
508 if (poll(pollfd, _FD_MAX, -1) < 0) {
515 if (pollfd[FD_INOTIFY].revents != 0)
518 if (pollfd[FD_SIGNAL].revents != 0)
525 static void help(void) {
526 printf("%s [OPTIONS...]\n\n"
527 "Process system password requests.\n\n"
528 " -h --help Show this help\n"
529 " --version Show package version\n"
530 " --list Show pending password requests\n"
531 " --query Process pending password requests\n"
532 " --watch Continuously process password requests\n"
533 " --wall Continuously forward password requests to wall\n"
534 " --plymouth Ask question with Plymouth instead of on TTY\n"
535 " --console Ask question on /dev/console instead of current TTY\n",
536 program_invocation_short_name);
539 static int parse_argv(int argc, char *argv[]) {
551 static const struct option options[] = {
552 { "help", no_argument, NULL, 'h' },
553 { "version", no_argument, NULL, ARG_VERSION },
554 { "list", no_argument, NULL, ARG_LIST },
555 { "query", no_argument, NULL, ARG_QUERY },
556 { "watch", no_argument, NULL, ARG_WATCH },
557 { "wall", no_argument, NULL, ARG_WALL },
558 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
559 { "console", no_argument, NULL, ARG_CONSOLE },
568 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
577 puts(PACKAGE_STRING);
578 puts(SYSTEMD_FEATURES);
582 arg_action = ACTION_LIST;
586 arg_action = ACTION_QUERY;
590 arg_action = ACTION_WATCH;
594 arg_action = ACTION_WALL;
609 assert_not_reached("Unhandled option");
612 if (optind != argc) {
613 log_error("%s takes no arguments.", program_invocation_short_name);
620 int main(int argc, char *argv[]) {
623 log_set_target(LOG_TARGET_AUTO);
624 log_parse_environment();
629 r = parse_argv(argc, argv);
638 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
639 r = watch_passwords();
641 r = show_passwords();
644 log_error("Error: %s", strerror(-r));
647 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;