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"
41 #include "path-util.h"
42 #include "process-util.h"
43 #include "signal-util.h"
44 #include "socket-util.h"
45 #include "string-util.h"
47 #include "terminal-util.h"
49 #include "utmp-wtmp.h"
56 } arg_action
= ACTION_QUERY
;
58 static bool arg_plymouth
= false;
59 static bool arg_console
= false;
61 static int ask_password_plymouth(
64 AskPasswordFlags flags
,
65 const char *flag_file
,
68 _cleanup_close_
int fd
= -1, notify
= -1;
69 union sockaddr_union sa
= PLYMOUTH_SOCKET
;
70 _cleanup_free_
char *packet
= NULL
;
73 struct pollfd pollfd
[2] = {};
74 char buffer
[LINE_MAX
];
84 notify
= inotify_init1(IN_CLOEXEC
|IN_NONBLOCK
);
88 r
= inotify_add_watch(notify
, flag_file
, IN_ATTRIB
); /* for the link count */
93 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
97 r
= connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + 1 + strlen(sa
.un
.sun_path
+1));
101 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
102 packet
= strdup("c");
104 } else if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0)
109 r
= loop_write(fd
, packet
, n
+ 1, true);
113 pollfd
[POLL_SOCKET
].fd
= fd
;
114 pollfd
[POLL_SOCKET
].events
= POLLIN
;
115 pollfd
[POLL_INOTIFY
].fd
= notify
;
116 pollfd
[POLL_INOTIFY
].events
= POLLIN
;
119 int sleep_for
= -1, j
;
124 y
= now(CLOCK_MONOTONIC
);
131 sleep_for
= (int) ((until
- y
) / USEC_PER_MSEC
);
134 if (flag_file
&& access(flag_file
, F_OK
) < 0) {
139 j
= poll(pollfd
, notify
>= 0 ? 2 : 1, sleep_for
);
151 if (notify
>= 0 && pollfd
[POLL_INOTIFY
].revents
!= 0)
154 if (pollfd
[POLL_SOCKET
].revents
== 0)
157 k
= read(fd
, buffer
+ p
, sizeof(buffer
) - p
);
159 if (errno
== EINTR
|| errno
== EAGAIN
)
174 if (buffer
[0] == 5) {
176 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
177 /* Hmm, first try with cached
178 * passwords failed, so let's retry
179 * with a normal password request */
180 packet
= mfree(packet
);
182 if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0) {
187 r
= loop_write(fd
, packet
, n
+1, true);
191 flags
&= ~ASK_PASSWORD_ACCEPT_CACHED
;
196 /* No password, because UI not shown */
200 } else if (buffer
[0] == 2 || buffer
[0] == 9) {
204 /* One or more answers */
208 memcpy(&size
, buffer
+1, sizeof(size
));
209 size
= le32toh(size
);
210 if (size
+ 5 > sizeof(buffer
)) {
218 l
= strv_parse_nulstr(buffer
+ 5, size
);
237 memory_erase(buffer
, sizeof(buffer
));
241 static int parse_password(const char *filename
, char **wall
) {
242 _cleanup_free_
char *socket_name
= NULL
, *message
= NULL
, *packet
= NULL
;
243 bool accept_cached
= false, echo
= false;
244 size_t packet_length
= 0;
245 uint64_t not_after
= 0;
248 const ConfigTableItem items
[] = {
249 { "Ask", "Socket", config_parse_string
, 0, &socket_name
},
250 { "Ask", "NotAfter", config_parse_uint64
, 0, ¬_after
},
251 { "Ask", "Message", config_parse_string
, 0, &message
},
252 { "Ask", "PID", config_parse_unsigned
, 0, &pid
},
253 { "Ask", "AcceptCached", config_parse_bool
, 0, &accept_cached
},
254 { "Ask", "Echo", config_parse_bool
, 0, &echo
},
262 r
= config_parse(NULL
, filename
, NULL
,
264 config_item_table_lookup
, items
,
265 true, false, true, NULL
);
270 log_error("Invalid password file %s", filename
);
274 if (not_after
> 0 && now(CLOCK_MONOTONIC
) > not_after
)
277 if (pid
> 0 && !pid_is_alive(pid
))
280 if (arg_action
== ACTION_LIST
)
281 printf("'%s' (PID %u)\n", message
, pid
);
283 else if (arg_action
== ACTION_WALL
) {
287 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
288 "Please enter password with the systemd-tty-ask-password-agent tool!",
290 *wall
? "\r\n\r\n" : "",
299 union sockaddr_union sa
= {};
300 _cleanup_close_
int socket_fd
= -1;
302 assert(arg_action
== ACTION_QUERY
||
303 arg_action
== ACTION_WATCH
);
305 if (access(socket_name
, W_OK
) < 0) {
306 if (arg_action
== ACTION_QUERY
)
307 log_info("Not querying '%s' (PID %u), lacking privileges.", message
, pid
);
313 _cleanup_strv_free_erase_
char **passwords
= NULL
;
315 r
= ask_password_plymouth(message
, not_after
, accept_cached
? ASK_PASSWORD_ACCEPT_CACHED
: 0, filename
, &passwords
);
320 STRV_FOREACH(p
, passwords
)
321 packet_length
+= strlen(*p
) + 1;
323 packet
= new(char, packet_length
);
327 char *d
= packet
+ 1;
329 STRV_FOREACH(p
, passwords
)
330 d
= stpcpy(d
, *p
) + 1;
337 _cleanup_string_free_erase_
char *password
= NULL
;
341 tty_fd
= acquire_terminal("/dev/console", false, false, false, USEC_INFINITY
);
343 return log_error_errno(tty_fd
, "Failed to acquire /dev/console: %m");
345 r
= reset_terminal_fd(tty_fd
, true);
347 log_warning_errno(r
, "Failed to reset terminal, ignoring: %m");
350 r
= ask_password_tty(message
, NULL
, not_after
, echo
? ASK_PASSWORD_ECHO
: 0, filename
, &password
);
353 tty_fd
= safe_close(tty_fd
);
358 packet_length
= 1 + strlen(password
) + 1;
359 packet
= new(char, packet_length
);
364 strcpy(packet
+ 1, password
);
369 if (IN_SET(r
, -ETIME
, -ENOENT
)) {
370 /* If the query went away, that's OK */
375 log_error_errno(r
, "Failed to query password: %m");
379 socket_fd
= socket(AF_UNIX
, SOCK_DGRAM
|SOCK_CLOEXEC
, 0);
381 r
= log_error_errno(errno
, "socket(): %m");
385 sa
.un
.sun_family
= AF_UNIX
;
386 strncpy(sa
.un
.sun_path
, socket_name
, sizeof(sa
.un
.sun_path
));
388 r
= sendto(socket_fd
, packet
, packet_length
, MSG_NOSIGNAL
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(socket_name
));
389 memory_erase(packet
, packet_length
);
391 return log_error_errno(errno
, "Failed to send: %m");
397 memory_erase(packet
, packet_length
);
401 static int wall_tty_block(void) {
402 _cleanup_free_
char *p
= NULL
;
406 r
= get_ctty_devnr(0, &devnr
);
407 if (r
== -ENXIO
) /* We have no controlling tty */
410 return log_error_errno(r
, "Failed to get controlling TTY: %m");
412 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(devnr
), minor(devnr
)) < 0)
415 mkdir_parents_label(p
, 0700);
418 fd
= open(p
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
420 return log_debug_errno(errno
, "Failed to open %s: %m", p
);
425 static bool wall_tty_match(const char *path
, void *userdata
) {
426 _cleanup_free_
char *p
= NULL
;
427 _cleanup_close_
int fd
= -1;
430 if (!path_is_absolute(path
))
431 path
= strjoina("/dev/", path
);
433 if (lstat(path
, &st
) < 0) {
434 log_debug_errno(errno
, "Failed to stat %s: %m", path
);
438 if (!S_ISCHR(st
.st_mode
)) {
439 log_debug("%s is not a character device.", path
);
443 /* We use named pipes to ensure that wall messages suggesting
444 * password entry are not printed over password prompts
445 * already shown. We use the fact here that opening a pipe in
446 * non-blocking mode for write-only will succeed only if
447 * there's some writer behind it. Using pipes has the
448 * advantage that the block will automatically go away if the
451 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(st
.st_rdev
), minor(st
.st_rdev
)) < 0) {
456 fd
= open(p
, O_WRONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
458 log_debug_errno(errno
, "Failed top open the wall pipe: %m");
462 /* What, we managed to open the pipe? Then this tty is filtered. */
466 static int show_passwords(void) {
467 _cleanup_closedir_
DIR *d
;
471 d
= opendir("/run/systemd/ask-password");
476 return log_error_errno(errno
, "Failed top open /run/systemd/ask-password: %m");
479 FOREACH_DIRENT_ALL(de
, d
, return log_error_errno(errno
, "Failed to read directory: %m")) {
480 _cleanup_free_
char *p
= NULL
, *wall
= NULL
;
483 /* We only support /dev on tmpfs, hence we can rely on
484 * d_type to be reliable */
486 if (de
->d_type
!= DT_REG
)
489 if (hidden_file(de
->d_name
))
492 if (!startswith(de
->d_name
, "ask."))
495 p
= strappend("/run/systemd/ask-password/", de
->d_name
);
499 q
= parse_password(p
, &wall
);
504 (void) utmp_wall(wall
, NULL
, NULL
, wall_tty_match
, NULL
);
510 static int watch_passwords(void) {
517 _cleanup_close_
int notify
= -1, signal_fd
= -1, tty_block_fd
= -1;
518 struct pollfd pollfd
[_FD_MAX
] = {};
522 tty_block_fd
= wall_tty_block();
524 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
526 notify
= inotify_init1(IN_CLOEXEC
);
528 return log_error_errno(errno
, "Failed to allocate directory watch: %m");
530 if (inotify_add_watch(notify
, "/run/systemd/ask-password", IN_CLOSE_WRITE
|IN_MOVED_TO
) < 0)
531 return log_error_errno(errno
, "Failed to add /run/systemd/ask-password to directory watch: %m");
533 assert_se(sigemptyset(&mask
) >= 0);
534 assert_se(sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1) >= 0);
535 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) >= 0);
537 signal_fd
= signalfd(-1, &mask
, SFD_NONBLOCK
|SFD_CLOEXEC
);
539 return log_error_errno(errno
, "Failed to allocate signal file descriptor: %m");
541 pollfd
[FD_INOTIFY
].fd
= notify
;
542 pollfd
[FD_INOTIFY
].events
= POLLIN
;
543 pollfd
[FD_SIGNAL
].fd
= signal_fd
;
544 pollfd
[FD_SIGNAL
].events
= POLLIN
;
547 r
= show_passwords();
549 log_error_errno(r
, "Failed to show password: %m");
551 if (poll(pollfd
, _FD_MAX
, -1) < 0) {
558 if (pollfd
[FD_INOTIFY
].revents
!= 0)
559 (void) flush_fd(notify
);
561 if (pollfd
[FD_SIGNAL
].revents
!= 0)
568 static void help(void) {
569 printf("%s [OPTIONS...]\n\n"
570 "Process system password requests.\n\n"
571 " -h --help Show this help\n"
572 " --version Show package version\n"
573 " --list Show pending password requests\n"
574 " --query Process pending password requests\n"
575 " --watch Continuously process password requests\n"
576 " --wall Continuously forward password requests to wall\n"
577 " --plymouth Ask question with Plymouth instead of on TTY\n"
578 " --console Ask question on /dev/console instead of current TTY\n",
579 program_invocation_short_name
);
582 static int parse_argv(int argc
, char *argv
[]) {
594 static const struct option options
[] = {
595 { "help", no_argument
, NULL
, 'h' },
596 { "version", no_argument
, NULL
, ARG_VERSION
},
597 { "list", no_argument
, NULL
, ARG_LIST
},
598 { "query", no_argument
, NULL
, ARG_QUERY
},
599 { "watch", no_argument
, NULL
, ARG_WATCH
},
600 { "wall", no_argument
, NULL
, ARG_WALL
},
601 { "plymouth", no_argument
, NULL
, ARG_PLYMOUTH
},
602 { "console", no_argument
, NULL
, ARG_CONSOLE
},
611 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
623 arg_action
= ACTION_LIST
;
627 arg_action
= ACTION_QUERY
;
631 arg_action
= ACTION_WATCH
;
635 arg_action
= ACTION_WALL
;
650 assert_not_reached("Unhandled option");
653 if (optind
!= argc
) {
654 log_error("%s takes no arguments.", program_invocation_short_name
);
661 int main(int argc
, char *argv
[]) {
664 log_set_target(LOG_TARGET_AUTO
);
665 log_parse_environment();
670 r
= parse_argv(argc
, argv
);
676 (void) release_terminal();
679 if (IN_SET(arg_action
, ACTION_WATCH
, ACTION_WALL
))
680 r
= watch_passwords();
682 r
= show_passwords();
685 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;