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"
44 #include "terminal-util.h"
46 #include "utmp-wtmp.h"
53 } arg_action
= ACTION_QUERY
;
55 static bool arg_plymouth
= false;
56 static bool arg_console
= false;
58 static int ask_password_plymouth(
61 AskPasswordFlags flags
,
62 const char *flag_file
,
65 _cleanup_close_
int fd
= -1, notify
= -1;
66 union sockaddr_union sa
= PLYMOUTH_SOCKET
;
67 _cleanup_free_
char *packet
= NULL
;
70 struct pollfd pollfd
[2] = {};
71 char buffer
[LINE_MAX
];
81 notify
= inotify_init1(IN_CLOEXEC
|IN_NONBLOCK
);
85 r
= inotify_add_watch(notify
, flag_file
, IN_ATTRIB
); /* for the link count */
90 fd
= socket(AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
94 r
= connect(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + 1 + strlen(sa
.un
.sun_path
+1));
98 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
101 } else if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0)
106 r
= loop_write(fd
, packet
, n
+ 1, true);
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 if (errno
== EINTR
|| errno
== EAGAIN
)
161 if (buffer
[0] == 5) {
163 if (flags
& ASK_PASSWORD_ACCEPT_CACHED
) {
164 /* Hmm, first try with cached
165 * passwords failed, so let's retry
166 * with a normal password request */
167 packet
= mfree(packet
);
169 if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0)
172 r
= loop_write(fd
, packet
, n
+1, true);
176 flags
&= ~ASK_PASSWORD_ACCEPT_CACHED
;
181 /* No password, because UI not shown */
184 } else if (buffer
[0] == 2 || buffer
[0] == 9) {
188 /* One or more answers */
192 memcpy(&size
, buffer
+1, sizeof(size
));
193 size
= le32toh(size
);
194 if (size
+ 5 > sizeof(buffer
))
200 l
= strv_parse_nulstr(buffer
+ 5, size
);
215 static int parse_password(const char *filename
, char **wall
) {
216 _cleanup_free_
char *socket_name
= NULL
, *message
= NULL
, *packet
= NULL
;
217 uint64_t not_after
= 0;
219 bool accept_cached
= false, echo
= false;
221 const ConfigTableItem items
[] = {
222 { "Ask", "Socket", config_parse_string
, 0, &socket_name
},
223 { "Ask", "NotAfter", config_parse_uint64
, 0, ¬_after
},
224 { "Ask", "Message", config_parse_string
, 0, &message
},
225 { "Ask", "PID", config_parse_unsigned
, 0, &pid
},
226 { "Ask", "AcceptCached", config_parse_bool
, 0, &accept_cached
},
227 { "Ask", "Echo", config_parse_bool
, 0, &echo
},
235 r
= config_parse(NULL
, filename
, NULL
,
237 config_item_table_lookup
, items
,
238 true, false, true, NULL
);
243 log_error("Invalid password file %s", filename
);
247 if (not_after
> 0 && now(CLOCK_MONOTONIC
) > not_after
)
250 if (pid
> 0 && !pid_is_alive(pid
))
253 if (arg_action
== ACTION_LIST
)
254 printf("'%s' (PID %u)\n", message
, pid
);
256 else if (arg_action
== ACTION_WALL
) {
260 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
261 "Please enter password with the systemd-tty-ask-password-agent tool!",
263 *wall
? "\r\n\r\n" : "",
272 union sockaddr_union sa
= {};
273 size_t packet_length
= 0;
274 _cleanup_close_
int socket_fd
= -1;
276 assert(arg_action
== ACTION_QUERY
||
277 arg_action
== ACTION_WATCH
);
279 if (access(socket_name
, W_OK
) < 0) {
280 if (arg_action
== ACTION_QUERY
)
281 log_info("Not querying '%s' (PID %u), lacking privileges.", message
, pid
);
287 _cleanup_strv_free_
char **passwords
= NULL
;
289 r
= ask_password_plymouth(message
, not_after
, accept_cached
? ASK_PASSWORD_ACCEPT_CACHED
: 0, filename
, &passwords
);
294 STRV_FOREACH(p
, passwords
)
295 packet_length
+= strlen(*p
) + 1;
297 packet
= new(char, packet_length
);
301 char *d
= packet
+ 1;
303 STRV_FOREACH(p
, passwords
)
304 d
= stpcpy(d
, *p
) + 1;
311 _cleanup_free_
char *password
= NULL
;
315 tty_fd
= acquire_terminal("/dev/console", false, false, false, USEC_INFINITY
);
317 return log_error_errno(tty_fd
, "Failed to acquire /dev/console: %m");
319 r
= reset_terminal_fd(tty_fd
, true);
321 log_warning_errno(r
, "Failed to reset terminal, ignoring: %m");
324 r
= ask_password_tty(message
, NULL
, not_after
, echo
? ASK_PASSWORD_ECHO
: 0, filename
, &password
);
327 tty_fd
= safe_close(tty_fd
);
332 packet_length
= 1 + strlen(password
) + 1;
333 packet
= new(char, packet_length
);
338 strcpy(packet
+ 1, password
);
343 if (IN_SET(r
, -ETIME
, -ENOENT
))
344 /* If the query went away, that's OK */
348 return log_error_errno(r
, "Failed to query password: %m");
350 socket_fd
= socket(AF_UNIX
, SOCK_DGRAM
|SOCK_CLOEXEC
, 0);
352 return log_error_errno(errno
, "socket(): %m");
354 sa
.un
.sun_family
= AF_UNIX
;
355 strncpy(sa
.un
.sun_path
, socket_name
, sizeof(sa
.un
.sun_path
));
357 r
= sendto(socket_fd
, packet
, packet_length
, MSG_NOSIGNAL
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(socket_name
));
359 return log_error_errno(errno
, "Failed to send: %m");
365 static int wall_tty_block(void) {
366 _cleanup_free_
char *p
= NULL
;
370 r
= get_ctty_devnr(0, &devnr
);
371 if (r
== -ENXIO
) /* We have no controlling tty */
374 return log_error_errno(r
, "Failed to get controlling TTY: %m");
376 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(devnr
), minor(devnr
)) < 0)
379 mkdir_parents_label(p
, 0700);
382 fd
= open(p
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
384 return log_debug_errno(errno
, "Failed to open %s: %m", p
);
389 static bool wall_tty_match(const char *path
, void *userdata
) {
390 _cleanup_free_
char *p
= NULL
;
391 _cleanup_close_
int fd
= -1;
394 if (!path_is_absolute(path
))
395 path
= strjoina("/dev/", path
);
397 if (lstat(path
, &st
) < 0) {
398 log_debug_errno(errno
, "Failed to stat %s: %m", path
);
402 if (!S_ISCHR(st
.st_mode
)) {
403 log_debug("%s is not a character device.", path
);
407 /* We use named pipes to ensure that wall messages suggesting
408 * password entry are not printed over password prompts
409 * already shown. We use the fact here that opening a pipe in
410 * non-blocking mode for write-only will succeed only if
411 * there's some writer behind it. Using pipes has the
412 * advantage that the block will automatically go away if the
415 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(st
.st_rdev
), minor(st
.st_rdev
)) < 0) {
420 fd
= open(p
, O_WRONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
422 log_debug_errno(errno
, "Failed top open the wall pipe: %m");
426 /* What, we managed to open the pipe? Then this tty is filtered. */
430 static int show_passwords(void) {
431 _cleanup_closedir_
DIR *d
;
435 d
= opendir("/run/systemd/ask-password");
440 return log_error_errno(errno
, "Failed top open /run/systemd/ask-password: %m");
443 FOREACH_DIRENT_ALL(de
, d
, return log_error_errno(errno
, "Failed to read directory: %m")) {
444 _cleanup_free_
char *p
= NULL
, *wall
= NULL
;
447 /* We only support /dev on tmpfs, hence we can rely on
448 * d_type to be reliable */
450 if (de
->d_type
!= DT_REG
)
453 if (hidden_file(de
->d_name
))
456 if (!startswith(de
->d_name
, "ask."))
459 p
= strappend("/run/systemd/ask-password/", de
->d_name
);
463 q
= parse_password(p
, &wall
);
468 (void) utmp_wall(wall
, NULL
, NULL
, wall_tty_match
, NULL
);
474 static int watch_passwords(void) {
481 _cleanup_close_
int notify
= -1, signal_fd
= -1, tty_block_fd
= -1;
482 struct pollfd pollfd
[_FD_MAX
] = {};
486 tty_block_fd
= wall_tty_block();
488 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
490 notify
= inotify_init1(IN_CLOEXEC
);
492 return log_error_errno(errno
, "Failed to allocate directory watch: %m");
494 if (inotify_add_watch(notify
, "/run/systemd/ask-password", IN_CLOSE_WRITE
|IN_MOVED_TO
) < 0)
495 return log_error_errno(errno
, "Failed to add /run/systemd/ask-password to directory watch: %m");
497 assert_se(sigemptyset(&mask
) >= 0);
498 assert_se(sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1) >= 0);
499 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) >= 0);
501 signal_fd
= signalfd(-1, &mask
, SFD_NONBLOCK
|SFD_CLOEXEC
);
503 return log_error_errno(errno
, "Failed to allocate signal file descriptor: %m");
505 pollfd
[FD_INOTIFY
].fd
= notify
;
506 pollfd
[FD_INOTIFY
].events
= POLLIN
;
507 pollfd
[FD_SIGNAL
].fd
= signal_fd
;
508 pollfd
[FD_SIGNAL
].events
= POLLIN
;
511 r
= show_passwords();
513 log_error_errno(r
, "Failed to show password: %m");
515 if (poll(pollfd
, _FD_MAX
, -1) < 0) {
522 if (pollfd
[FD_INOTIFY
].revents
!= 0)
523 (void) flush_fd(notify
);
525 if (pollfd
[FD_SIGNAL
].revents
!= 0)
532 static void help(void) {
533 printf("%s [OPTIONS...]\n\n"
534 "Process system password requests.\n\n"
535 " -h --help Show this help\n"
536 " --version Show package version\n"
537 " --list Show pending password requests\n"
538 " --query Process pending password requests\n"
539 " --watch Continuously process password requests\n"
540 " --wall Continuously forward password requests to wall\n"
541 " --plymouth Ask question with Plymouth instead of on TTY\n"
542 " --console Ask question on /dev/console instead of current TTY\n",
543 program_invocation_short_name
);
546 static int parse_argv(int argc
, char *argv
[]) {
558 static const struct option options
[] = {
559 { "help", no_argument
, NULL
, 'h' },
560 { "version", no_argument
, NULL
, ARG_VERSION
},
561 { "list", no_argument
, NULL
, ARG_LIST
},
562 { "query", no_argument
, NULL
, ARG_QUERY
},
563 { "watch", no_argument
, NULL
, ARG_WATCH
},
564 { "wall", no_argument
, NULL
, ARG_WALL
},
565 { "plymouth", no_argument
, NULL
, ARG_PLYMOUTH
},
566 { "console", no_argument
, NULL
, ARG_CONSOLE
},
575 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
587 arg_action
= ACTION_LIST
;
591 arg_action
= ACTION_QUERY
;
595 arg_action
= ACTION_WATCH
;
599 arg_action
= ACTION_WALL
;
614 assert_not_reached("Unhandled option");
617 if (optind
!= argc
) {
618 log_error("%s takes no arguments.", program_invocation_short_name
);
625 int main(int argc
, char *argv
[]) {
628 log_set_target(LOG_TARGET_AUTO
);
629 log_parse_environment();
634 r
= parse_argv(argc
, argv
);
640 (void) release_terminal();
643 if (IN_SET(arg_action
, ACTION_WATCH
, ACTION_WALL
))
644 r
= watch_passwords();
646 r
= show_passwords();
649 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;