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))) {
245 static int parse_password(const char *filename, char **wall) {
246 char *socket_name = NULL, *message = NULL, *packet = NULL;
247 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 }
266 f = fopen(filename, "re");
271 log_error("open(%s): %m", filename);
275 r = config_parse(NULL, filename, f, NULL, config_item_table_lookup, (void*) items, true, false, NULL);
277 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
282 log_error("Invalid password file %s", filename);
288 if (now(CLOCK_MONOTONIC) > not_after) {
294 if (pid > 0 && !pid_is_alive(pid)) {
299 if (arg_action == ACTION_LIST)
300 printf("'%s' (PID %u)\n", message, pid);
301 else if (arg_action == ACTION_WALL) {
305 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
306 "Please enter password with the systemd-tty-ask-password-agent tool!",
308 *wall ? "\r\n\r\n" : "",
320 struct sockaddr_un un;
322 size_t packet_length = 0;
324 assert(arg_action == ACTION_QUERY ||
325 arg_action == ACTION_WATCH);
327 if (access(socket_name, W_OK) < 0) {
329 if (arg_action == ACTION_QUERY)
330 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
337 _cleanup_strv_free_ char **passwords = NULL;
339 if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
343 STRV_FOREACH(p, passwords)
344 packet_length += strlen(*p) + 1;
346 if (!(packet = new(char, packet_length)))
354 STRV_FOREACH(p, passwords)
355 d = stpcpy(d, *p) + 1;
361 char *password = NULL;
364 if ((tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1)) < 0) {
369 r = ask_password_tty(message, not_after, filename, &password);
377 packet_length = 1+strlen(password)+1;
378 if (!(packet = new(char, packet_length)))
382 strcpy(packet+1, password);
389 if (r == -ETIME || r == -ENOENT) {
390 /* If the query went away, that's OK */
396 log_error("Failed to query password: %s", strerror(-r));
400 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
401 log_error("socket(): %m");
406 sa.un.sun_family = AF_UNIX;
407 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
409 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
410 log_error("Failed to send: %m");
419 safe_close(socket_fd);
428 static int wall_tty_block(void) {
433 r = get_ctty_devnr(0, &devnr);
437 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
440 mkdir_parents_label(p, 0700);
443 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
452 static bool wall_tty_match(const char *path) {
457 if (path_is_absolute(path))
458 k = lstat(path, &st);
460 if (asprintf(&p, "/dev/%s", path) < 0)
470 if (!S_ISCHR(st.st_mode))
473 /* We use named pipes to ensure that wall messages suggesting
474 * password entry are not printed over password prompts
475 * already shown. We use the fact here that opening a pipe in
476 * non-blocking mode for write-only will succeed only if
477 * there's some writer behind it. Using pipes has the
478 * advantage that the block will automatically go away if the
481 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
484 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
490 /* What, we managed to open the pipe? Then this tty is filtered. */
495 static int show_passwords(void) {
500 if (!(d = opendir("/run/systemd/ask-password"))) {
504 log_error("opendir(/run/systemd/ask-password): %m");
508 while ((de = readdir(d))) {
513 /* We only support /dev on tmpfs, hence we can rely on
514 * d_type to be reliable */
516 if (de->d_type != DT_REG)
519 if (ignore_file(de->d_name))
522 if (!startswith(de->d_name, "ask."))
525 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
531 if ((q = parse_password(p, &wall)) < 0)
537 utmp_wall(wall, NULL, wall_tty_match);
549 static int watch_passwords(void) {
556 int notify = -1, signal_fd = -1, tty_block_fd = -1;
557 struct pollfd pollfd[_FD_MAX] = {};
561 tty_block_fd = wall_tty_block();
563 mkdir_p_label("/run/systemd/ask-password", 0755);
565 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
570 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
575 assert_se(sigemptyset(&mask) == 0);
576 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
577 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
579 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
580 log_error("signalfd(): %m");
585 pollfd[FD_INOTIFY].fd = notify;
586 pollfd[FD_INOTIFY].events = POLLIN;
587 pollfd[FD_SIGNAL].fd = signal_fd;
588 pollfd[FD_SIGNAL].events = POLLIN;
591 if ((r = show_passwords()) < 0)
592 log_error("Failed to show password: %s", strerror(-r));
594 if (poll(pollfd, _FD_MAX, -1) < 0) {
603 if (pollfd[FD_INOTIFY].revents != 0)
606 if (pollfd[FD_SIGNAL].revents != 0)
614 safe_close(signal_fd);
615 safe_close(tty_block_fd);
620 static int help(void) {
622 printf("%s [OPTIONS...]\n\n"
623 "Process system password requests.\n\n"
624 " -h --help Show this help\n"
625 " --version Show package version\n"
626 " --list Show pending password requests\n"
627 " --query Process pending password requests\n"
628 " --watch Continuously process password requests\n"
629 " --wall Continuously forward password requests to wall\n"
630 " --plymouth Ask question with Plymouth instead of on TTY\n"
631 " --console Ask question on /dev/console instead of current TTY\n",
632 program_invocation_short_name);
637 static int parse_argv(int argc, char *argv[]) {
649 static const struct option options[] = {
650 { "help", no_argument, NULL, 'h' },
651 { "version", no_argument, NULL, ARG_VERSION },
652 { "list", no_argument, NULL, ARG_LIST },
653 { "query", no_argument, NULL, ARG_QUERY },
654 { "watch", no_argument, NULL, ARG_WATCH },
655 { "wall", no_argument, NULL, ARG_WALL },
656 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
657 { "console", no_argument, NULL, ARG_CONSOLE },
666 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
674 puts(PACKAGE_STRING);
675 puts(SYSTEMD_FEATURES);
679 arg_action = ACTION_LIST;
683 arg_action = ACTION_QUERY;
687 arg_action = ACTION_WATCH;
691 arg_action = ACTION_WALL;
706 assert_not_reached("Unhandled option");
710 if (optind != argc) {
718 int main(int argc, char *argv[]) {
721 log_set_target(LOG_TARGET_AUTO);
722 log_parse_environment();
727 if ((r = parse_argv(argc, argv)) <= 0)
735 if (arg_action == ACTION_WATCH ||
736 arg_action == ACTION_WALL)
737 r = watch_passwords();
739 r = show_passwords();
742 log_error("Error: %s", strerror(-r));
745 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;