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 return log_error_errno(errno, "Failed to connect to Plymouth: %m");
99 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1),
106 k = loop_write(fd, packet, n + 1, true);
108 return k < 0 ? (int) k : -EIO;
110 pollfd[POLL_SOCKET].fd = fd;
111 pollfd[POLL_SOCKET].events = POLLIN;
112 pollfd[POLL_INOTIFY].fd = notify;
113 pollfd[POLL_INOTIFY].events = POLLIN;
116 int sleep_for = -1, j;
121 y = now(CLOCK_MONOTONIC);
126 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
129 if (flag_file && access(flag_file, F_OK) < 0)
132 j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
141 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
144 if (pollfd[POLL_SOCKET].revents == 0)
147 k = read(fd, buffer + p, sizeof(buffer) - p);
149 return r = k < 0 ? -errno : -EIO;
156 if (buffer[0] == 5) {
159 /* Hmm, first try with cached
160 * passwords failed, so let's retry
161 * with a normal password request */
165 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
168 k = loop_write(fd, packet, n+1, true);
170 return k < 0 ? (int) k : -EIO;
172 accept_cached = false;
177 /* No password, because UI not shown */
180 } else if (buffer[0] == 2 || buffer[0] == 9) {
184 /* One or more answers */
188 memcpy(&size, buffer+1, sizeof(size));
189 size = le32toh(size);
190 if (size + 5 > sizeof(buffer))
196 l = strv_parse_nulstr(buffer + 5, size);
211 static int parse_password(const char *filename, char **wall) {
212 _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL;
213 uint64_t not_after = 0;
215 bool accept_cached = false, echo = false;
217 const ConfigTableItem items[] = {
218 { "Ask", "Socket", config_parse_string, 0, &socket_name },
219 { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after },
220 { "Ask", "Message", config_parse_string, 0, &message },
221 { "Ask", "PID", config_parse_unsigned, 0, &pid },
222 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
223 { "Ask", "Echo", config_parse_bool, 0, &echo },
231 r = config_parse(NULL, filename, NULL,
233 config_item_table_lookup, items,
234 true, false, true, NULL);
239 log_error("Invalid password file %s", filename);
243 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
246 if (pid > 0 && !pid_is_alive(pid))
249 if (arg_action == ACTION_LIST)
250 printf("'%s' (PID %u)\n", message, pid);
252 else if (arg_action == ACTION_WALL) {
256 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
257 "Please enter password with the systemd-tty-ask-password-agent tool!",
259 *wall ? "\r\n\r\n" : "",
268 union sockaddr_union sa = {};
269 size_t packet_length = 0;
270 _cleanup_close_ int socket_fd = -1;
272 assert(arg_action == ACTION_QUERY ||
273 arg_action == ACTION_WATCH);
275 if (access(socket_name, W_OK) < 0) {
276 if (arg_action == ACTION_QUERY)
277 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
283 _cleanup_strv_free_ char **passwords = NULL;
285 r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
290 STRV_FOREACH(p, passwords)
291 packet_length += strlen(*p) + 1;
293 packet = new(char, packet_length);
297 char *d = packet + 1;
299 STRV_FOREACH(p, passwords)
300 d = stpcpy(d, *p) + 1;
308 _cleanup_free_ char *password = NULL;
311 tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
316 r = ask_password_tty(message, not_after, echo, filename, &password);
324 packet_length = 1 + strlen(password) + 1;
325 packet = new(char, packet_length);
330 strcpy(packet + 1, password);
335 if (IN_SET(r, -ETIME, -ENOENT))
336 /* If the query went away, that's OK */
340 return log_error_errno(r, "Failed to query password: %m");
342 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
344 return log_error_errno(errno, "socket(): %m");
346 sa.un.sun_family = AF_UNIX;
347 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
349 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
350 offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
352 log_error_errno(errno, "Failed to send: %m");
360 static int wall_tty_block(void) {
361 _cleanup_free_ char *p = NULL;
365 r = get_ctty_devnr(0, &devnr);
369 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
372 mkdir_parents_label(p, 0700);
375 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
382 static bool wall_tty_match(const char *path) {
385 _cleanup_free_ char *p = NULL;
387 if (!path_is_absolute(path))
388 path = strappenda("/dev/", path);
390 r = lstat(path, &st);
394 if (!S_ISCHR(st.st_mode))
397 /* We use named pipes to ensure that wall messages suggesting
398 * password entry are not printed over password prompts
399 * already shown. We use the fact here that opening a pipe in
400 * non-blocking mode for write-only will succeed only if
401 * there's some writer behind it. Using pipes has the
402 * advantage that the block will automatically go away if the
405 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
408 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
412 /* What, we managed to open the pipe? Then this tty is filtered. */
417 static int show_passwords(void) {
418 _cleanup_closedir_ DIR *d;
422 d = opendir("/run/systemd/ask-password");
427 log_error_errno(errno, "opendir(/run/systemd/ask-password): %m");
431 while ((de = readdir(d))) {
432 _cleanup_free_ char *p = NULL, *wall = NULL;
435 /* We only support /dev on tmpfs, hence we can rely on
436 * d_type to be reliable */
438 if (de->d_type != DT_REG)
441 if (ignore_file(de->d_name))
444 if (!startswith(de->d_name, "ask."))
447 p = strappend("/run/systemd/ask-password/", de->d_name);
451 q = parse_password(p, &wall);
456 utmp_wall(wall, NULL, wall_tty_match);
462 static int watch_passwords(void) {
469 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
470 struct pollfd pollfd[_FD_MAX] = {};
474 tty_block_fd = wall_tty_block();
476 mkdir_p_label("/run/systemd/ask-password", 0755);
478 notify = inotify_init1(IN_CLOEXEC);
482 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
485 assert_se(sigemptyset(&mask) == 0);
486 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
487 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
489 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
493 pollfd[FD_INOTIFY].fd = notify;
494 pollfd[FD_INOTIFY].events = POLLIN;
495 pollfd[FD_SIGNAL].fd = signal_fd;
496 pollfd[FD_SIGNAL].events = POLLIN;
499 r = show_passwords();
501 log_error_errno(r, "Failed to show password: %m");
503 if (poll(pollfd, _FD_MAX, -1) < 0) {
510 if (pollfd[FD_INOTIFY].revents != 0)
513 if (pollfd[FD_SIGNAL].revents != 0)
520 static void help(void) {
521 printf("%s [OPTIONS...]\n\n"
522 "Process system password requests.\n\n"
523 " -h --help Show this help\n"
524 " --version Show package version\n"
525 " --list Show pending password requests\n"
526 " --query Process pending password requests\n"
527 " --watch Continuously process password requests\n"
528 " --wall Continuously forward password requests to wall\n"
529 " --plymouth Ask question with Plymouth instead of on TTY\n"
530 " --console Ask question on /dev/console instead of current TTY\n",
531 program_invocation_short_name);
534 static int parse_argv(int argc, char *argv[]) {
546 static const struct option options[] = {
547 { "help", no_argument, NULL, 'h' },
548 { "version", no_argument, NULL, ARG_VERSION },
549 { "list", no_argument, NULL, ARG_LIST },
550 { "query", no_argument, NULL, ARG_QUERY },
551 { "watch", no_argument, NULL, ARG_WATCH },
552 { "wall", no_argument, NULL, ARG_WALL },
553 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
554 { "console", no_argument, NULL, ARG_CONSOLE },
563 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
572 puts(PACKAGE_STRING);
573 puts(SYSTEMD_FEATURES);
577 arg_action = ACTION_LIST;
581 arg_action = ACTION_QUERY;
585 arg_action = ACTION_WATCH;
589 arg_action = ACTION_WALL;
604 assert_not_reached("Unhandled option");
607 if (optind != argc) {
608 log_error("%s takes no arguments.", program_invocation_short_name);
615 int main(int argc, char *argv[]) {
618 log_set_target(LOG_TARGET_AUTO);
619 log_parse_environment();
624 r = parse_argv(argc, argv);
633 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
634 r = watch_passwords();
636 r = show_passwords();
639 log_error_errno(r, "Error: %m");
642 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;