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 log_error_errno(errno
, "Failed to connect to Plymouth: %m");
101 } else if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1),
108 k
= loop_write(fd
, packet
, n
+ 1, true);
110 return k
< 0 ? (int) k
: -EIO
;
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
);
128 sleep_for
= (int) ((until
- y
) / USEC_PER_MSEC
);
131 if (flag_file
&& access(flag_file
, F_OK
) < 0)
134 j
= poll(pollfd
, notify
> 0 ? 2 : 1, sleep_for
);
143 if (notify
> 0 && pollfd
[POLL_INOTIFY
].revents
!= 0)
146 if (pollfd
[POLL_SOCKET
].revents
== 0)
149 k
= read(fd
, buffer
+ p
, sizeof(buffer
) - p
);
151 return r
= k
< 0 ? -errno
: -EIO
;
158 if (buffer
[0] == 5) {
161 /* Hmm, first try with cached
162 * passwords failed, so let's retry
163 * with a normal password request */
167 if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0)
170 k
= loop_write(fd
, packet
, n
+1, true);
172 return k
< 0 ? (int) k
: -EIO
;
174 accept_cached
= false;
179 /* No password, because UI not shown */
182 } else if (buffer
[0] == 2 || buffer
[0] == 9) {
186 /* One or more answers */
190 memcpy(&size
, buffer
+1, sizeof(size
));
191 size
= le32toh(size
);
192 if (size
+ 5 > sizeof(buffer
))
198 l
= strv_parse_nulstr(buffer
+ 5, size
);
213 static int parse_password(const char *filename
, char **wall
) {
214 _cleanup_free_
char *socket_name
= NULL
, *message
= NULL
, *packet
= NULL
;
215 uint64_t not_after
= 0;
217 bool accept_cached
= false, echo
= false;
219 const ConfigTableItem items
[] = {
220 { "Ask", "Socket", config_parse_string
, 0, &socket_name
},
221 { "Ask", "NotAfter", config_parse_uint64
, 0, ¬_after
},
222 { "Ask", "Message", config_parse_string
, 0, &message
},
223 { "Ask", "PID", config_parse_unsigned
, 0, &pid
},
224 { "Ask", "AcceptCached", config_parse_bool
, 0, &accept_cached
},
225 { "Ask", "Echo", config_parse_bool
, 0, &echo
},
233 r
= config_parse(NULL
, filename
, NULL
,
235 config_item_table_lookup
, items
,
236 true, false, true, NULL
);
241 log_error("Invalid password file %s", filename
);
245 if (not_after
> 0 && now(CLOCK_MONOTONIC
) > not_after
)
248 if (pid
> 0 && !pid_is_alive(pid
))
251 if (arg_action
== ACTION_LIST
)
252 printf("'%s' (PID %u)\n", message
, pid
);
254 else if (arg_action
== ACTION_WALL
) {
258 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
259 "Please enter password with the systemd-tty-ask-password-agent tool!",
261 *wall
? "\r\n\r\n" : "",
270 union sockaddr_union sa
= {};
271 size_t packet_length
= 0;
272 _cleanup_close_
int socket_fd
= -1;
274 assert(arg_action
== ACTION_QUERY
||
275 arg_action
== ACTION_WATCH
);
277 if (access(socket_name
, W_OK
) < 0) {
278 if (arg_action
== ACTION_QUERY
)
279 log_info("Not querying '%s' (PID %u), lacking privileges.", message
, pid
);
285 _cleanup_strv_free_
char **passwords
= NULL
;
287 r
= ask_password_plymouth(message
, not_after
, filename
, accept_cached
, &passwords
);
292 STRV_FOREACH(p
, passwords
)
293 packet_length
+= strlen(*p
) + 1;
295 packet
= new(char, packet_length
);
299 char *d
= packet
+ 1;
301 STRV_FOREACH(p
, passwords
)
302 d
= stpcpy(d
, *p
) + 1;
310 _cleanup_free_
char *password
= NULL
;
313 tty_fd
= acquire_terminal("/dev/console", false, false, false, USEC_INFINITY
);
318 r
= ask_password_tty(message
, not_after
, echo
, filename
, &password
);
326 packet_length
= 1 + strlen(password
) + 1;
327 packet
= new(char, packet_length
);
332 strcpy(packet
+ 1, password
);
337 if (IN_SET(r
, -ETIME
, -ENOENT
))
338 /* If the query went away, that's OK */
342 return log_error_errno(r
, "Failed to query password: %m");
344 socket_fd
= socket(AF_UNIX
, SOCK_DGRAM
|SOCK_CLOEXEC
, 0);
346 log_error_errno(errno
, "socket(): %m");
350 sa
.un
.sun_family
= AF_UNIX
;
351 strncpy(sa
.un
.sun_path
, socket_name
, sizeof(sa
.un
.sun_path
));
353 r
= sendto(socket_fd
, packet
, packet_length
, MSG_NOSIGNAL
, &sa
.sa
,
354 offsetof(struct sockaddr_un
, sun_path
) + strlen(socket_name
));
356 log_error_errno(errno
, "Failed to send: %m");
364 static int wall_tty_block(void) {
365 _cleanup_free_
char *p
= NULL
;
369 r
= get_ctty_devnr(0, &devnr
);
373 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(devnr
), minor(devnr
)) < 0)
376 mkdir_parents_label(p
, 0700);
379 fd
= open(p
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
386 static bool wall_tty_match(const char *path
) {
389 _cleanup_free_
char *p
= NULL
;
391 if (!path_is_absolute(path
))
392 path
= strappenda("/dev/", path
);
394 r
= lstat(path
, &st
);
398 if (!S_ISCHR(st
.st_mode
))
401 /* We use named pipes to ensure that wall messages suggesting
402 * password entry are not printed over password prompts
403 * already shown. We use the fact here that opening a pipe in
404 * non-blocking mode for write-only will succeed only if
405 * there's some writer behind it. Using pipes has the
406 * advantage that the block will automatically go away if the
409 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(st
.st_rdev
), minor(st
.st_rdev
)) < 0)
412 fd
= open(p
, O_WRONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
416 /* What, we managed to open the pipe? Then this tty is filtered. */
421 static int show_passwords(void) {
422 _cleanup_closedir_
DIR *d
;
426 d
= opendir("/run/systemd/ask-password");
431 log_error_errno(errno
, "opendir(/run/systemd/ask-password): %m");
435 while ((de
= readdir(d
))) {
436 _cleanup_free_
char *p
= NULL
, *wall
= NULL
;
439 /* We only support /dev on tmpfs, hence we can rely on
440 * d_type to be reliable */
442 if (de
->d_type
!= DT_REG
)
445 if (ignore_file(de
->d_name
))
448 if (!startswith(de
->d_name
, "ask."))
451 p
= strappend("/run/systemd/ask-password/", de
->d_name
);
455 q
= parse_password(p
, &wall
);
460 utmp_wall(wall
, NULL
, wall_tty_match
);
466 static int watch_passwords(void) {
473 _cleanup_close_
int notify
= -1, signal_fd
= -1, tty_block_fd
= -1;
474 struct pollfd pollfd
[_FD_MAX
] = {};
478 tty_block_fd
= wall_tty_block();
480 mkdir_p_label("/run/systemd/ask-password", 0755);
482 notify
= inotify_init1(IN_CLOEXEC
);
486 if (inotify_add_watch(notify
, "/run/systemd/ask-password", IN_CLOSE_WRITE
|IN_MOVED_TO
) < 0)
489 assert_se(sigemptyset(&mask
) == 0);
490 sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1);
491 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) == 0);
493 signal_fd
= signalfd(-1, &mask
, SFD_NONBLOCK
|SFD_CLOEXEC
);
497 pollfd
[FD_INOTIFY
].fd
= notify
;
498 pollfd
[FD_INOTIFY
].events
= POLLIN
;
499 pollfd
[FD_SIGNAL
].fd
= signal_fd
;
500 pollfd
[FD_SIGNAL
].events
= POLLIN
;
503 r
= show_passwords();
505 log_error_errno(r
, "Failed to show password: %m");
507 if (poll(pollfd
, _FD_MAX
, -1) < 0) {
514 if (pollfd
[FD_INOTIFY
].revents
!= 0)
517 if (pollfd
[FD_SIGNAL
].revents
!= 0)
524 static void help(void) {
525 printf("%s [OPTIONS...]\n\n"
526 "Process system password requests.\n\n"
527 " -h --help Show this help\n"
528 " --version Show package version\n"
529 " --list Show pending password requests\n"
530 " --query Process pending password requests\n"
531 " --watch Continuously process password requests\n"
532 " --wall Continuously forward password requests to wall\n"
533 " --plymouth Ask question with Plymouth instead of on TTY\n"
534 " --console Ask question on /dev/console instead of current TTY\n",
535 program_invocation_short_name
);
538 static int parse_argv(int argc
, char *argv
[]) {
550 static const struct option options
[] = {
551 { "help", no_argument
, NULL
, 'h' },
552 { "version", no_argument
, NULL
, ARG_VERSION
},
553 { "list", no_argument
, NULL
, ARG_LIST
},
554 { "query", no_argument
, NULL
, ARG_QUERY
},
555 { "watch", no_argument
, NULL
, ARG_WATCH
},
556 { "wall", no_argument
, NULL
, ARG_WALL
},
557 { "plymouth", no_argument
, NULL
, ARG_PLYMOUTH
},
558 { "console", no_argument
, NULL
, ARG_CONSOLE
},
567 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
576 puts(PACKAGE_STRING
);
577 puts(SYSTEMD_FEATURES
);
581 arg_action
= ACTION_LIST
;
585 arg_action
= ACTION_QUERY
;
589 arg_action
= ACTION_WATCH
;
593 arg_action
= ACTION_WALL
;
608 assert_not_reached("Unhandled option");
611 if (optind
!= argc
) {
612 log_error("%s takes no arguments.", program_invocation_short_name
);
619 int main(int argc
, char *argv
[]) {
622 log_set_target(LOG_TARGET_AUTO
);
623 log_parse_environment();
628 r
= parse_argv(argc
, argv
);
637 if (IN_SET(arg_action
, ACTION_WATCH
, ACTION_WALL
))
638 r
= watch_passwords();
640 r
= show_passwords();
643 log_error_errno(r
, "Error: %m");
646 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;