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"
39 #include "path-util.h"
40 #include "process-util.h"
41 #include "signal-util.h"
42 #include "socket-util.h"
43 #include "string-util.h"
45 #include "terminal-util.h"
47 #include "utmp-wtmp.h"
54 } arg_action
= ACTION_QUERY
;
56 static bool arg_plymouth
= false;
57 static bool arg_console
= false;
59 static int ask_password_plymouth(
62 AskPasswordFlags flags
,
63 const char *flag_file
,
66 _cleanup_close_
int fd
= -1, notify
= -1;
67 union sockaddr_union sa
= PLYMOUTH_SOCKET
;
68 _cleanup_free_
char *packet
= NULL
;
71 struct pollfd pollfd
[2] = {};
72 char buffer
[LINE_MAX
];
82 notify
= inotify_init1(IN_CLOEXEC
|IN_NONBLOCK
);
86 r
= inotify_add_watch(notify
, flag_file
, IN_ATTRIB
); /* for the link count */
91 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
95 r
= connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + 1 + strlen(sa
.un
.sun_path
+1));
99 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
100 packet
= strdup("c");
102 } else if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0)
107 r
= loop_write(fd
, packet
, n
+ 1, true);
111 pollfd
[POLL_SOCKET
].fd
= fd
;
112 pollfd
[POLL_SOCKET
].events
= POLLIN
;
113 pollfd
[POLL_INOTIFY
].fd
= notify
;
114 pollfd
[POLL_INOTIFY
].events
= POLLIN
;
117 int sleep_for
= -1, j
;
122 y
= now(CLOCK_MONOTONIC
);
129 sleep_for
= (int) ((until
- y
) / USEC_PER_MSEC
);
132 if (flag_file
&& access(flag_file
, F_OK
) < 0) {
137 j
= poll(pollfd
, notify
>= 0 ? 2 : 1, sleep_for
);
149 if (notify
>= 0 && pollfd
[POLL_INOTIFY
].revents
!= 0)
152 if (pollfd
[POLL_SOCKET
].revents
== 0)
155 k
= read(fd
, buffer
+ p
, sizeof(buffer
) - p
);
157 if (errno
== EINTR
|| errno
== EAGAIN
)
172 if (buffer
[0] == 5) {
174 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
175 /* Hmm, first try with cached
176 * passwords failed, so let's retry
177 * with a normal password request */
178 packet
= mfree(packet
);
180 if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0) {
185 r
= loop_write(fd
, packet
, n
+1, true);
189 flags
&= ~ASK_PASSWORD_ACCEPT_CACHED
;
194 /* No password, because UI not shown */
198 } else if (buffer
[0] == 2 || buffer
[0] == 9) {
202 /* One or more answers */
206 memcpy(&size
, buffer
+1, sizeof(size
));
207 size
= le32toh(size
);
208 if (size
+ 5 > sizeof(buffer
)) {
216 l
= strv_parse_nulstr(buffer
+ 5, size
);
235 memory_erase(buffer
, sizeof(buffer
));
239 static int parse_password(const char *filename
, char **wall
) {
240 _cleanup_free_
char *socket_name
= NULL
, *message
= NULL
, *packet
= NULL
;
241 bool accept_cached
= false, echo
= false;
242 size_t packet_length
= 0;
243 uint64_t not_after
= 0;
246 const ConfigTableItem items
[] = {
247 { "Ask", "Socket", config_parse_string
, 0, &socket_name
},
248 { "Ask", "NotAfter", config_parse_uint64
, 0, ¬_after
},
249 { "Ask", "Message", config_parse_string
, 0, &message
},
250 { "Ask", "PID", config_parse_unsigned
, 0, &pid
},
251 { "Ask", "AcceptCached", config_parse_bool
, 0, &accept_cached
},
252 { "Ask", "Echo", config_parse_bool
, 0, &echo
},
260 r
= config_parse(NULL
, filename
, NULL
,
262 config_item_table_lookup
, items
,
263 true, false, true, NULL
);
268 log_error("Invalid password file %s", filename
);
272 if (not_after
> 0 && now(CLOCK_MONOTONIC
) > not_after
)
275 if (pid
> 0 && !pid_is_alive(pid
))
278 if (arg_action
== ACTION_LIST
)
279 printf("'%s' (PID %u)\n", message
, pid
);
281 else if (arg_action
== ACTION_WALL
) {
285 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
286 "Please enter password with the systemd-tty-ask-password-agent tool!",
288 *wall
? "\r\n\r\n" : "",
297 union sockaddr_union sa
= {};
298 _cleanup_close_
int socket_fd
= -1;
300 assert(arg_action
== ACTION_QUERY
||
301 arg_action
== ACTION_WATCH
);
303 if (access(socket_name
, W_OK
) < 0) {
304 if (arg_action
== ACTION_QUERY
)
305 log_info("Not querying '%s' (PID %u), lacking privileges.", message
, pid
);
311 _cleanup_strv_free_erase_
char **passwords
= NULL
;
313 r
= ask_password_plymouth(message
, not_after
, accept_cached
? ASK_PASSWORD_ACCEPT_CACHED
: 0, filename
, &passwords
);
318 STRV_FOREACH(p
, passwords
)
319 packet_length
+= strlen(*p
) + 1;
321 packet
= new(char, packet_length
);
325 char *d
= packet
+ 1;
327 STRV_FOREACH(p
, passwords
)
328 d
= stpcpy(d
, *p
) + 1;
335 _cleanup_string_free_erase_
char *password
= NULL
;
339 tty_fd
= acquire_terminal("/dev/console", false, false, false, USEC_INFINITY
);
341 return log_error_errno(tty_fd
, "Failed to acquire /dev/console: %m");
343 r
= reset_terminal_fd(tty_fd
, true);
345 log_warning_errno(r
, "Failed to reset terminal, ignoring: %m");
348 r
= ask_password_tty(message
, NULL
, not_after
, echo
? ASK_PASSWORD_ECHO
: 0, filename
, &password
);
351 tty_fd
= safe_close(tty_fd
);
356 packet_length
= 1 + strlen(password
) + 1;
357 packet
= new(char, packet_length
);
362 strcpy(packet
+ 1, password
);
367 if (IN_SET(r
, -ETIME
, -ENOENT
)) {
368 /* If the query went away, that's OK */
373 log_error_errno(r
, "Failed to query password: %m");
377 socket_fd
= socket(AF_UNIX
, SOCK_DGRAM
|SOCK_CLOEXEC
, 0);
379 r
= log_error_errno(errno
, "socket(): %m");
383 sa
.un
.sun_family
= AF_UNIX
;
384 strncpy(sa
.un
.sun_path
, socket_name
, sizeof(sa
.un
.sun_path
));
386 r
= sendto(socket_fd
, packet
, packet_length
, MSG_NOSIGNAL
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(socket_name
));
387 memory_erase(packet
, packet_length
);
389 return log_error_errno(errno
, "Failed to send: %m");
395 memory_erase(packet
, packet_length
);
399 static int wall_tty_block(void) {
400 _cleanup_free_
char *p
= NULL
;
404 r
= get_ctty_devnr(0, &devnr
);
405 if (r
== -ENXIO
) /* We have no controlling tty */
408 return log_error_errno(r
, "Failed to get controlling TTY: %m");
410 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(devnr
), minor(devnr
)) < 0)
413 mkdir_parents_label(p
, 0700);
416 fd
= open(p
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
418 return log_debug_errno(errno
, "Failed to open %s: %m", p
);
423 static bool wall_tty_match(const char *path
, void *userdata
) {
424 _cleanup_free_
char *p
= NULL
;
425 _cleanup_close_
int fd
= -1;
428 if (!path_is_absolute(path
))
429 path
= strjoina("/dev/", path
);
431 if (lstat(path
, &st
) < 0) {
432 log_debug_errno(errno
, "Failed to stat %s: %m", path
);
436 if (!S_ISCHR(st
.st_mode
)) {
437 log_debug("%s is not a character device.", path
);
441 /* We use named pipes to ensure that wall messages suggesting
442 * password entry are not printed over password prompts
443 * already shown. We use the fact here that opening a pipe in
444 * non-blocking mode for write-only will succeed only if
445 * there's some writer behind it. Using pipes has the
446 * advantage that the block will automatically go away if the
449 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(st
.st_rdev
), minor(st
.st_rdev
)) < 0) {
454 fd
= open(p
, O_WRONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
456 log_debug_errno(errno
, "Failed top open the wall pipe: %m");
460 /* What, we managed to open the pipe? Then this tty is filtered. */
464 static int show_passwords(void) {
465 _cleanup_closedir_
DIR *d
;
469 d
= opendir("/run/systemd/ask-password");
474 return log_error_errno(errno
, "Failed top open /run/systemd/ask-password: %m");
477 FOREACH_DIRENT_ALL(de
, d
, return log_error_errno(errno
, "Failed to read directory: %m")) {
478 _cleanup_free_
char *p
= NULL
, *wall
= NULL
;
481 /* We only support /dev on tmpfs, hence we can rely on
482 * d_type to be reliable */
484 if (de
->d_type
!= DT_REG
)
487 if (hidden_file(de
->d_name
))
490 if (!startswith(de
->d_name
, "ask."))
493 p
= strappend("/run/systemd/ask-password/", de
->d_name
);
497 q
= parse_password(p
, &wall
);
502 (void) utmp_wall(wall
, NULL
, NULL
, wall_tty_match
, NULL
);
508 static int watch_passwords(void) {
515 _cleanup_close_
int notify
= -1, signal_fd
= -1, tty_block_fd
= -1;
516 struct pollfd pollfd
[_FD_MAX
] = {};
520 tty_block_fd
= wall_tty_block();
522 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
524 notify
= inotify_init1(IN_CLOEXEC
);
526 return log_error_errno(errno
, "Failed to allocate directory watch: %m");
528 if (inotify_add_watch(notify
, "/run/systemd/ask-password", IN_CLOSE_WRITE
|IN_MOVED_TO
) < 0)
529 return log_error_errno(errno
, "Failed to add /run/systemd/ask-password to directory watch: %m");
531 assert_se(sigemptyset(&mask
) >= 0);
532 assert_se(sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1) >= 0);
533 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) >= 0);
535 signal_fd
= signalfd(-1, &mask
, SFD_NONBLOCK
|SFD_CLOEXEC
);
537 return log_error_errno(errno
, "Failed to allocate signal file descriptor: %m");
539 pollfd
[FD_INOTIFY
].fd
= notify
;
540 pollfd
[FD_INOTIFY
].events
= POLLIN
;
541 pollfd
[FD_SIGNAL
].fd
= signal_fd
;
542 pollfd
[FD_SIGNAL
].events
= POLLIN
;
545 r
= show_passwords();
547 log_error_errno(r
, "Failed to show password: %m");
549 if (poll(pollfd
, _FD_MAX
, -1) < 0) {
556 if (pollfd
[FD_INOTIFY
].revents
!= 0)
557 (void) flush_fd(notify
);
559 if (pollfd
[FD_SIGNAL
].revents
!= 0)
566 static void help(void) {
567 printf("%s [OPTIONS...]\n\n"
568 "Process system password requests.\n\n"
569 " -h --help Show this help\n"
570 " --version Show package version\n"
571 " --list Show pending password requests\n"
572 " --query Process pending password requests\n"
573 " --watch Continuously process password requests\n"
574 " --wall Continuously forward password requests to wall\n"
575 " --plymouth Ask question with Plymouth instead of on TTY\n"
576 " --console Ask question on /dev/console instead of current TTY\n",
577 program_invocation_short_name
);
580 static int parse_argv(int argc
, char *argv
[]) {
592 static const struct option options
[] = {
593 { "help", no_argument
, NULL
, 'h' },
594 { "version", no_argument
, NULL
, ARG_VERSION
},
595 { "list", no_argument
, NULL
, ARG_LIST
},
596 { "query", no_argument
, NULL
, ARG_QUERY
},
597 { "watch", no_argument
, NULL
, ARG_WATCH
},
598 { "wall", no_argument
, NULL
, ARG_WALL
},
599 { "plymouth", no_argument
, NULL
, ARG_PLYMOUTH
},
600 { "console", no_argument
, NULL
, ARG_CONSOLE
},
609 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
621 arg_action
= ACTION_LIST
;
625 arg_action
= ACTION_QUERY
;
629 arg_action
= ACTION_WATCH
;
633 arg_action
= ACTION_WALL
;
648 assert_not_reached("Unhandled option");
651 if (optind
!= argc
) {
652 log_error("%s takes no arguments.", program_invocation_short_name
);
659 int main(int argc
, char *argv
[]) {
662 log_set_target(LOG_TARGET_AUTO
);
663 log_parse_environment();
668 r
= parse_argv(argc
, argv
);
674 (void) release_terminal();
677 if (IN_SET(arg_action
, ACTION_WATCH
, ACTION_WALL
))
678 r
= watch_passwords();
680 r
= show_passwords();
683 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;