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/>.
29 #include <sys/inotify.h>
30 #include <sys/signalfd.h>
31 #include <sys/socket.h>
35 #include "ask-password-api.h"
36 #include "conf-parser.h"
40 #include "path-util.h"
41 #include "process-util.h"
42 #include "signal-util.h"
43 #include "socket-util.h"
44 #include "string-util.h"
46 #include "terminal-util.h"
48 #include "utmp-wtmp.h"
55 } arg_action
= ACTION_QUERY
;
57 static bool arg_plymouth
= false;
58 static bool arg_console
= false;
60 static int ask_password_plymouth(
63 AskPasswordFlags flags
,
64 const char *flag_file
,
67 _cleanup_close_
int fd
= -1, notify
= -1;
68 union sockaddr_union sa
= PLYMOUTH_SOCKET
;
69 _cleanup_free_
char *packet
= NULL
;
72 struct pollfd pollfd
[2] = {};
73 char buffer
[LINE_MAX
];
83 notify
= inotify_init1(IN_CLOEXEC
|IN_NONBLOCK
);
87 r
= inotify_add_watch(notify
, flag_file
, IN_ATTRIB
); /* for the link count */
92 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
96 r
= connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + 1 + strlen(sa
.un
.sun_path
+1));
100 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
101 packet
= strdup("c");
103 } else if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0)
108 r
= loop_write(fd
, packet
, n
+ 1, true);
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
);
130 sleep_for
= (int) ((until
- y
) / USEC_PER_MSEC
);
133 if (flag_file
&& access(flag_file
, F_OK
) < 0) {
138 j
= poll(pollfd
, notify
>= 0 ? 2 : 1, sleep_for
);
150 if (notify
>= 0 && pollfd
[POLL_INOTIFY
].revents
!= 0)
153 if (pollfd
[POLL_SOCKET
].revents
== 0)
156 k
= read(fd
, buffer
+ p
, sizeof(buffer
) - p
);
158 if (errno
== EINTR
|| errno
== EAGAIN
)
173 if (buffer
[0] == 5) {
175 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
176 /* Hmm, first try with cached
177 * passwords failed, so let's retry
178 * with a normal password request */
179 packet
= mfree(packet
);
181 if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0) {
186 r
= loop_write(fd
, packet
, n
+1, true);
190 flags
&= ~ASK_PASSWORD_ACCEPT_CACHED
;
195 /* No password, because UI not shown */
199 } else if (buffer
[0] == 2 || buffer
[0] == 9) {
203 /* One or more answers */
207 memcpy(&size
, buffer
+1, sizeof(size
));
208 size
= le32toh(size
);
209 if (size
+ 5 > sizeof(buffer
)) {
217 l
= strv_parse_nulstr(buffer
+ 5, size
);
236 memory_erase(buffer
, sizeof(buffer
));
240 static int parse_password(const char *filename
, char **wall
) {
241 _cleanup_free_
char *socket_name
= NULL
, *message
= NULL
, *packet
= NULL
;
242 bool accept_cached
= false, echo
= false;
243 size_t packet_length
= 0;
244 uint64_t not_after
= 0;
247 const ConfigTableItem items
[] = {
248 { "Ask", "Socket", config_parse_string
, 0, &socket_name
},
249 { "Ask", "NotAfter", config_parse_uint64
, 0, ¬_after
},
250 { "Ask", "Message", config_parse_string
, 0, &message
},
251 { "Ask", "PID", config_parse_unsigned
, 0, &pid
},
252 { "Ask", "AcceptCached", config_parse_bool
, 0, &accept_cached
},
253 { "Ask", "Echo", config_parse_bool
, 0, &echo
},
261 r
= config_parse(NULL
, filename
, NULL
,
263 config_item_table_lookup
, items
,
264 true, false, true, NULL
);
269 log_error("Invalid password file %s", filename
);
273 if (not_after
> 0 && now(CLOCK_MONOTONIC
) > not_after
)
276 if (pid
> 0 && !pid_is_alive(pid
))
279 if (arg_action
== ACTION_LIST
)
280 printf("'%s' (PID %u)\n", message
, pid
);
282 else if (arg_action
== ACTION_WALL
) {
286 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
287 "Please enter password with the systemd-tty-ask-password-agent tool!",
289 *wall
? "\r\n\r\n" : "",
298 union sockaddr_union sa
= {};
299 _cleanup_close_
int socket_fd
= -1;
301 assert(arg_action
== ACTION_QUERY
||
302 arg_action
== ACTION_WATCH
);
304 if (access(socket_name
, W_OK
) < 0) {
305 if (arg_action
== ACTION_QUERY
)
306 log_info("Not querying '%s' (PID %u), lacking privileges.", message
, pid
);
312 _cleanup_strv_free_erase_
char **passwords
= NULL
;
314 r
= ask_password_plymouth(message
, not_after
, accept_cached
? ASK_PASSWORD_ACCEPT_CACHED
: 0, filename
, &passwords
);
319 STRV_FOREACH(p
, passwords
)
320 packet_length
+= strlen(*p
) + 1;
322 packet
= new(char, packet_length
);
326 char *d
= packet
+ 1;
328 STRV_FOREACH(p
, passwords
)
329 d
= stpcpy(d
, *p
) + 1;
336 _cleanup_string_free_erase_
char *password
= NULL
;
340 tty_fd
= acquire_terminal("/dev/console", false, false, false, USEC_INFINITY
);
342 return log_error_errno(tty_fd
, "Failed to acquire /dev/console: %m");
344 r
= reset_terminal_fd(tty_fd
, true);
346 log_warning_errno(r
, "Failed to reset terminal, ignoring: %m");
349 r
= ask_password_tty(message
, NULL
, not_after
, echo
? ASK_PASSWORD_ECHO
: 0, filename
, &password
);
352 tty_fd
= safe_close(tty_fd
);
357 packet_length
= 1 + strlen(password
) + 1;
358 packet
= new(char, packet_length
);
363 strcpy(packet
+ 1, password
);
368 if (IN_SET(r
, -ETIME
, -ENOENT
)) {
369 /* If the query went away, that's OK */
374 log_error_errno(r
, "Failed to query password: %m");
378 socket_fd
= socket(AF_UNIX
, SOCK_DGRAM
|SOCK_CLOEXEC
, 0);
380 r
= log_error_errno(errno
, "socket(): %m");
384 sa
.un
.sun_family
= AF_UNIX
;
385 strncpy(sa
.un
.sun_path
, socket_name
, sizeof(sa
.un
.sun_path
));
387 r
= sendto(socket_fd
, packet
, packet_length
, MSG_NOSIGNAL
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(socket_name
));
388 memory_erase(packet
, packet_length
);
390 return log_error_errno(errno
, "Failed to send: %m");
396 memory_erase(packet
, packet_length
);
400 static int wall_tty_block(void) {
401 _cleanup_free_
char *p
= NULL
;
405 r
= get_ctty_devnr(0, &devnr
);
406 if (r
== -ENXIO
) /* We have no controlling tty */
409 return log_error_errno(r
, "Failed to get controlling TTY: %m");
411 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(devnr
), minor(devnr
)) < 0)
414 mkdir_parents_label(p
, 0700);
417 fd
= open(p
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
419 return log_debug_errno(errno
, "Failed to open %s: %m", p
);
424 static bool wall_tty_match(const char *path
, void *userdata
) {
425 _cleanup_free_
char *p
= NULL
;
426 _cleanup_close_
int fd
= -1;
429 if (!path_is_absolute(path
))
430 path
= strjoina("/dev/", path
);
432 if (lstat(path
, &st
) < 0) {
433 log_debug_errno(errno
, "Failed to stat %s: %m", path
);
437 if (!S_ISCHR(st
.st_mode
)) {
438 log_debug("%s is not a character device.", path
);
442 /* We use named pipes to ensure that wall messages suggesting
443 * password entry are not printed over password prompts
444 * already shown. We use the fact here that opening a pipe in
445 * non-blocking mode for write-only will succeed only if
446 * there's some writer behind it. Using pipes has the
447 * advantage that the block will automatically go away if the
450 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
);
457 log_debug_errno(errno
, "Failed top open the wall pipe: %m");
461 /* What, we managed to open the pipe? Then this tty is filtered. */
465 static int show_passwords(void) {
466 _cleanup_closedir_
DIR *d
;
470 d
= opendir("/run/systemd/ask-password");
475 return log_error_errno(errno
, "Failed top open /run/systemd/ask-password: %m");
478 FOREACH_DIRENT_ALL(de
, d
, return log_error_errno(errno
, "Failed to read directory: %m")) {
479 _cleanup_free_
char *p
= NULL
, *wall
= NULL
;
482 /* We only support /dev on tmpfs, hence we can rely on
483 * d_type to be reliable */
485 if (de
->d_type
!= DT_REG
)
488 if (hidden_file(de
->d_name
))
491 if (!startswith(de
->d_name
, "ask."))
494 p
= strappend("/run/systemd/ask-password/", de
->d_name
);
498 q
= parse_password(p
, &wall
);
503 (void) utmp_wall(wall
, NULL
, NULL
, wall_tty_match
, NULL
);
509 static int watch_passwords(void) {
516 _cleanup_close_
int notify
= -1, signal_fd
= -1, tty_block_fd
= -1;
517 struct pollfd pollfd
[_FD_MAX
] = {};
521 tty_block_fd
= wall_tty_block();
523 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
525 notify
= inotify_init1(IN_CLOEXEC
);
527 return log_error_errno(errno
, "Failed to allocate directory watch: %m");
529 if (inotify_add_watch(notify
, "/run/systemd/ask-password", IN_CLOSE_WRITE
|IN_MOVED_TO
) < 0)
530 return log_error_errno(errno
, "Failed to add /run/systemd/ask-password to directory watch: %m");
532 assert_se(sigemptyset(&mask
) >= 0);
533 assert_se(sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1) >= 0);
534 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) >= 0);
536 signal_fd
= signalfd(-1, &mask
, SFD_NONBLOCK
|SFD_CLOEXEC
);
538 return log_error_errno(errno
, "Failed to allocate signal file descriptor: %m");
540 pollfd
[FD_INOTIFY
].fd
= notify
;
541 pollfd
[FD_INOTIFY
].events
= POLLIN
;
542 pollfd
[FD_SIGNAL
].fd
= signal_fd
;
543 pollfd
[FD_SIGNAL
].events
= POLLIN
;
546 r
= show_passwords();
548 log_error_errno(r
, "Failed to show password: %m");
550 if (poll(pollfd
, _FD_MAX
, -1) < 0) {
557 if (pollfd
[FD_INOTIFY
].revents
!= 0)
558 (void) flush_fd(notify
);
560 if (pollfd
[FD_SIGNAL
].revents
!= 0)
567 static void help(void) {
568 printf("%s [OPTIONS...]\n\n"
569 "Process system password requests.\n\n"
570 " -h --help Show this help\n"
571 " --version Show package version\n"
572 " --list Show pending password requests\n"
573 " --query Process pending password requests\n"
574 " --watch Continuously process password requests\n"
575 " --wall Continuously forward password requests to wall\n"
576 " --plymouth Ask question with Plymouth instead of on TTY\n"
577 " --console Ask question on /dev/console instead of current TTY\n",
578 program_invocation_short_name
);
581 static int parse_argv(int argc
, char *argv
[]) {
593 static const struct option options
[] = {
594 { "help", no_argument
, NULL
, 'h' },
595 { "version", no_argument
, NULL
, ARG_VERSION
},
596 { "list", no_argument
, NULL
, ARG_LIST
},
597 { "query", no_argument
, NULL
, ARG_QUERY
},
598 { "watch", no_argument
, NULL
, ARG_WATCH
},
599 { "wall", no_argument
, NULL
, ARG_WALL
},
600 { "plymouth", no_argument
, NULL
, ARG_PLYMOUTH
},
601 { "console", no_argument
, NULL
, ARG_CONSOLE
},
610 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
622 arg_action
= ACTION_LIST
;
626 arg_action
= ACTION_QUERY
;
630 arg_action
= ACTION_WATCH
;
634 arg_action
= ACTION_WALL
;
649 assert_not_reached("Unhandled option");
652 if (optind
!= argc
) {
653 log_error("%s takes no arguments.", program_invocation_short_name
);
660 int main(int argc
, char *argv
[]) {
663 log_set_target(LOG_TARGET_AUTO
);
664 log_parse_environment();
669 r
= parse_argv(argc
, argv
);
675 (void) release_terminal();
678 if (IN_SET(arg_action
, ACTION_WATCH
, ACTION_WALL
))
679 r
= watch_passwords();
681 r
= show_passwords();
684 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;