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) {
297 if (pid > 0 && !pid_is_alive(pid)) {
302 if (arg_action == ACTION_LIST)
303 printf("'%s' (PID %u)\n", message, pid);
304 else if (arg_action == ACTION_WALL) {
308 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
309 "Please enter password with the systemd-tty-ask-password-agent tool!",
311 *wall ? "\r\n\r\n" : "",
323 struct sockaddr_un un;
325 size_t packet_length = 0;
327 assert(arg_action == ACTION_QUERY ||
328 arg_action == ACTION_WATCH);
330 if (access(socket_name, W_OK) < 0) {
332 if (arg_action == ACTION_QUERY)
333 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
340 _cleanup_strv_free_ char **passwords = NULL;
342 if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
346 STRV_FOREACH(p, passwords)
347 packet_length += strlen(*p) + 1;
349 if (!(packet = new(char, packet_length)))
357 STRV_FOREACH(p, passwords)
358 d = stpcpy(d, *p) + 1;
364 char *password = NULL;
367 if ((tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1)) < 0) {
372 r = ask_password_tty(message, not_after, filename, &password);
375 close_nointr_nofail(tty_fd);
380 packet_length = 1+strlen(password)+1;
381 if (!(packet = new(char, packet_length)))
385 strcpy(packet+1, password);
392 if (r == -ETIME || r == -ENOENT) {
393 /* If the query went away, that's OK */
399 log_error("Failed to query password: %s", strerror(-r));
403 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
404 log_error("socket(): %m");
409 sa.un.sun_family = AF_UNIX;
410 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
412 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
413 log_error("Failed to send: %m");
423 close_nointr_nofail(socket_fd);
432 static int wall_tty_block(void) {
437 r = get_ctty_devnr(0, &devnr);
441 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
444 mkdir_parents_label(p, 0700);
447 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
456 static bool wall_tty_match(const char *path) {
461 if (path_is_absolute(path))
462 k = lstat(path, &st);
464 if (asprintf(&p, "/dev/%s", path) < 0)
474 if (!S_ISCHR(st.st_mode))
477 /* We use named pipes to ensure that wall messages suggesting
478 * password entry are not printed over password prompts
479 * already shown. We use the fact here that opening a pipe in
480 * non-blocking mode for write-only will succeed only if
481 * there's some writer behind it. Using pipes has the
482 * advantage that the block will automatically go away if the
485 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
488 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
494 /* What, we managed to open the pipe? Then this tty is filtered. */
495 close_nointr_nofail(fd);
499 static int show_passwords(void) {
504 if (!(d = opendir("/run/systemd/ask-password"))) {
508 log_error("opendir(): %m");
512 while ((de = readdir(d))) {
517 /* We only support /dev on tmpfs, hence we can rely on
518 * d_type to be reliable */
520 if (de->d_type != DT_REG)
523 if (ignore_file(de->d_name))
526 if (!startswith(de->d_name, "ask."))
529 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
535 if ((q = parse_password(p, &wall)) < 0)
541 utmp_wall(wall, NULL, wall_tty_match);
553 static int watch_passwords(void) {
560 int notify = -1, signal_fd = -1, tty_block_fd = -1;
561 struct pollfd pollfd[_FD_MAX] = {};
565 tty_block_fd = wall_tty_block();
567 mkdir_p_label("/run/systemd/ask-password", 0755);
569 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
574 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
579 assert_se(sigemptyset(&mask) == 0);
580 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
581 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
583 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
584 log_error("signalfd(): %m");
589 pollfd[FD_INOTIFY].fd = notify;
590 pollfd[FD_INOTIFY].events = POLLIN;
591 pollfd[FD_SIGNAL].fd = signal_fd;
592 pollfd[FD_SIGNAL].events = POLLIN;
595 if ((r = show_passwords()) < 0)
596 log_error("Failed to show password: %s", strerror(-r));
598 if (poll(pollfd, _FD_MAX, -1) < 0) {
607 if (pollfd[FD_INOTIFY].revents != 0)
610 if (pollfd[FD_SIGNAL].revents != 0)
618 close_nointr_nofail(notify);
621 close_nointr_nofail(signal_fd);
623 if (tty_block_fd >= 0)
624 close_nointr_nofail(tty_block_fd);
629 static int help(void) {
631 printf("%s [OPTIONS...]\n\n"
632 "Process system password requests.\n\n"
633 " -h --help Show this help\n"
634 " --version Show package version\n"
635 " --list Show pending password requests\n"
636 " --query Process pending password requests\n"
637 " --watch Continuously process password requests\n"
638 " --wall Continuously forward password requests to wall\n"
639 " --plymouth Ask question with Plymouth instead of on TTY\n"
640 " --console Ask question on /dev/console instead of current TTY\n",
641 program_invocation_short_name);
646 static int parse_argv(int argc, char *argv[]) {
658 static const struct option options[] = {
659 { "help", no_argument, NULL, 'h' },
660 { "version", no_argument, NULL, ARG_VERSION },
661 { "list", no_argument, NULL, ARG_LIST },
662 { "query", no_argument, NULL, ARG_QUERY },
663 { "watch", no_argument, NULL, ARG_WATCH },
664 { "wall", no_argument, NULL, ARG_WALL },
665 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
666 { "console", no_argument, NULL, ARG_CONSOLE },
675 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
683 puts(PACKAGE_STRING);
684 puts(SYSTEMD_FEATURES);
688 arg_action = ACTION_LIST;
692 arg_action = ACTION_QUERY;
696 arg_action = ACTION_WATCH;
700 arg_action = ACTION_WALL;
715 assert_not_reached("Unhandled option");
719 if (optind != argc) {
727 int main(int argc, char *argv[]) {
730 log_set_target(LOG_TARGET_AUTO);
731 log_parse_environment();
736 if ((r = parse_argv(argc, argv)) <= 0)
744 if (arg_action == ACTION_WATCH ||
745 arg_action == ACTION_WALL)
746 r = watch_passwords();
748 r = show_passwords();
751 log_error("Error: %s", strerror(-r));
754 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;