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 const char *flag_file
,
63 char ***_passphrases
) {
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));
96 return log_error_errno(errno
, "Failed to connect to Plymouth: %m");
101 } else if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1),
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
);
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 */
164 packet
= mfree(packet
);
166 if (asprintf(&packet
, "*\002%c%s%n", (int) (strlen(message
) + 1), message
, &n
) < 0)
169 r
= loop_write(fd
, packet
, n
+1, true);
173 accept_cached
= false;
178 /* No password, because UI not shown */
181 } else if (buffer
[0] == 2 || buffer
[0] == 9) {
185 /* One or more answers */
189 memcpy(&size
, buffer
+1, sizeof(size
));
190 size
= le32toh(size
);
191 if (size
+ 5 > sizeof(buffer
))
197 l
= strv_parse_nulstr(buffer
+ 5, size
);
212 static int parse_password(const char *filename
, char **wall
) {
213 _cleanup_free_
char *socket_name
= NULL
, *message
= NULL
, *packet
= NULL
;
214 uint64_t not_after
= 0;
216 bool accept_cached
= false, echo
= false;
218 const ConfigTableItem items
[] = {
219 { "Ask", "Socket", config_parse_string
, 0, &socket_name
},
220 { "Ask", "NotAfter", config_parse_uint64
, 0, ¬_after
},
221 { "Ask", "Message", config_parse_string
, 0, &message
},
222 { "Ask", "PID", config_parse_unsigned
, 0, &pid
},
223 { "Ask", "AcceptCached", config_parse_bool
, 0, &accept_cached
},
224 { "Ask", "Echo", config_parse_bool
, 0, &echo
},
232 r
= config_parse(NULL
, filename
, NULL
,
234 config_item_table_lookup
, items
,
235 true, false, true, NULL
);
240 log_error("Invalid password file %s", filename
);
244 if (not_after
> 0 && now(CLOCK_MONOTONIC
) > not_after
)
247 if (pid
> 0 && !pid_is_alive(pid
))
250 if (arg_action
== ACTION_LIST
)
251 printf("'%s' (PID %u)\n", message
, pid
);
253 else if (arg_action
== ACTION_WALL
) {
257 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
258 "Please enter password with the systemd-tty-ask-password-agent tool!",
260 *wall
? "\r\n\r\n" : "",
269 union sockaddr_union sa
= {};
270 size_t packet_length
= 0;
271 _cleanup_close_
int socket_fd
= -1;
273 assert(arg_action
== ACTION_QUERY
||
274 arg_action
== ACTION_WATCH
);
276 if (access(socket_name
, W_OK
) < 0) {
277 if (arg_action
== ACTION_QUERY
)
278 log_info("Not querying '%s' (PID %u), lacking privileges.", message
, pid
);
284 _cleanup_strv_free_
char **passwords
= NULL
;
286 r
= ask_password_plymouth(message
, not_after
, filename
, accept_cached
, &passwords
);
291 STRV_FOREACH(p
, passwords
)
292 packet_length
+= strlen(*p
) + 1;
294 packet
= new(char, packet_length
);
298 char *d
= packet
+ 1;
300 STRV_FOREACH(p
, passwords
)
301 d
= stpcpy(d
, *p
) + 1;
309 _cleanup_free_
char *password
= NULL
;
312 tty_fd
= acquire_terminal("/dev/console", false, false, false, USEC_INFINITY
);
317 r
= ask_password_tty(message
, not_after
, echo
, filename
, &password
);
325 packet_length
= 1 + strlen(password
) + 1;
326 packet
= new(char, packet_length
);
331 strcpy(packet
+ 1, password
);
336 if (IN_SET(r
, -ETIME
, -ENOENT
))
337 /* If the query went away, that's OK */
341 return log_error_errno(r
, "Failed to query password: %m");
343 socket_fd
= socket(AF_UNIX
, SOCK_DGRAM
|SOCK_CLOEXEC
, 0);
345 return log_error_errno(errno
, "socket(): %m");
347 sa
.un
.sun_family
= AF_UNIX
;
348 strncpy(sa
.un
.sun_path
, socket_name
, sizeof(sa
.un
.sun_path
));
350 r
= sendto(socket_fd
, packet
, packet_length
, MSG_NOSIGNAL
, &sa
.sa
,
351 offsetof(struct sockaddr_un
, sun_path
) + strlen(socket_name
));
353 log_error_errno(errno
, "Failed to send: %m");
361 static int wall_tty_block(void) {
362 _cleanup_free_
char *p
= NULL
;
366 r
= get_ctty_devnr(0, &devnr
);
370 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(devnr
), minor(devnr
)) < 0)
373 mkdir_parents_label(p
, 0700);
376 fd
= open(p
, O_RDONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
383 static bool wall_tty_match(const char *path
, void *userdata
) {
386 _cleanup_free_
char *p
= NULL
;
388 if (!path_is_absolute(path
))
389 path
= strjoina("/dev/", path
);
391 r
= lstat(path
, &st
);
395 if (!S_ISCHR(st
.st_mode
))
398 /* We use named pipes to ensure that wall messages suggesting
399 * password entry are not printed over password prompts
400 * already shown. We use the fact here that opening a pipe in
401 * non-blocking mode for write-only will succeed only if
402 * there's some writer behind it. Using pipes has the
403 * advantage that the block will automatically go away if the
406 if (asprintf(&p
, "/run/systemd/ask-password-block/%u:%u", major(st
.st_rdev
), minor(st
.st_rdev
)) < 0)
409 fd
= open(p
, O_WRONLY
|O_CLOEXEC
|O_NONBLOCK
|O_NOCTTY
);
413 /* What, we managed to open the pipe? Then this tty is filtered. */
418 static int show_passwords(void) {
419 _cleanup_closedir_
DIR *d
;
423 d
= opendir("/run/systemd/ask-password");
428 log_error_errno(errno
, "opendir(/run/systemd/ask-password): %m");
432 while ((de
= readdir(d
))) {
433 _cleanup_free_
char *p
= NULL
, *wall
= NULL
;
436 /* We only support /dev on tmpfs, hence we can rely on
437 * d_type to be reliable */
439 if (de
->d_type
!= DT_REG
)
442 if (hidden_file(de
->d_name
))
445 if (!startswith(de
->d_name
, "ask."))
448 p
= strappend("/run/systemd/ask-password/", de
->d_name
);
452 q
= parse_password(p
, &wall
);
457 utmp_wall(wall
, NULL
, NULL
, wall_tty_match
, NULL
);
463 static int watch_passwords(void) {
470 _cleanup_close_
int notify
= -1, signal_fd
= -1, tty_block_fd
= -1;
471 struct pollfd pollfd
[_FD_MAX
] = {};
475 tty_block_fd
= wall_tty_block();
477 mkdir_p_label("/run/systemd/ask-password", 0755);
479 notify
= inotify_init1(IN_CLOEXEC
);
483 if (inotify_add_watch(notify
, "/run/systemd/ask-password", IN_CLOSE_WRITE
|IN_MOVED_TO
) < 0)
486 assert_se(sigemptyset(&mask
) >= 0);
487 assert_se(sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1) >= 0);
488 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) >= 0);
490 signal_fd
= signalfd(-1, &mask
, SFD_NONBLOCK
|SFD_CLOEXEC
);
494 pollfd
[FD_INOTIFY
].fd
= notify
;
495 pollfd
[FD_INOTIFY
].events
= POLLIN
;
496 pollfd
[FD_SIGNAL
].fd
= signal_fd
;
497 pollfd
[FD_SIGNAL
].events
= POLLIN
;
500 r
= show_passwords();
502 log_error_errno(r
, "Failed to show password: %m");
504 if (poll(pollfd
, _FD_MAX
, -1) < 0) {
511 if (pollfd
[FD_INOTIFY
].revents
!= 0)
514 if (pollfd
[FD_SIGNAL
].revents
!= 0)
521 static void help(void) {
522 printf("%s [OPTIONS...]\n\n"
523 "Process system password requests.\n\n"
524 " -h --help Show this help\n"
525 " --version Show package version\n"
526 " --list Show pending password requests\n"
527 " --query Process pending password requests\n"
528 " --watch Continuously process password requests\n"
529 " --wall Continuously forward password requests to wall\n"
530 " --plymouth Ask question with Plymouth instead of on TTY\n"
531 " --console Ask question on /dev/console instead of current TTY\n",
532 program_invocation_short_name
);
535 static int parse_argv(int argc
, char *argv
[]) {
547 static const struct option options
[] = {
548 { "help", no_argument
, NULL
, 'h' },
549 { "version", no_argument
, NULL
, ARG_VERSION
},
550 { "list", no_argument
, NULL
, ARG_LIST
},
551 { "query", no_argument
, NULL
, ARG_QUERY
},
552 { "watch", no_argument
, NULL
, ARG_WATCH
},
553 { "wall", no_argument
, NULL
, ARG_WALL
},
554 { "plymouth", no_argument
, NULL
, ARG_PLYMOUTH
},
555 { "console", no_argument
, NULL
, ARG_CONSOLE
},
564 while ((c
= getopt_long(argc
, argv
, "h", options
, NULL
)) >= 0)
576 arg_action
= ACTION_LIST
;
580 arg_action
= ACTION_QUERY
;
584 arg_action
= ACTION_WATCH
;
588 arg_action
= ACTION_WALL
;
603 assert_not_reached("Unhandled option");
606 if (optind
!= argc
) {
607 log_error("%s takes no arguments.", program_invocation_short_name
);
614 int main(int argc
, char *argv
[]) {
617 log_set_target(LOG_TARGET_AUTO
);
618 log_parse_environment();
623 r
= parse_argv(argc
, argv
);
629 (void) release_terminal();
632 if (IN_SET(arg_action
, ACTION_WATCH
, ACTION_WALL
))
633 r
= watch_passwords();
635 r
= show_passwords();
637 log_error_errno(r
, "Error: %m");
640 return r
< 0 ? EXIT_FAILURE
: EXIT_SUCCESS
;