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/>.
34 #include <sys/inotify.h>
35 #include <sys/signalfd.h>
36 #include <sys/socket.h>
44 #include "alloc-util.h"
45 #include "ask-password-api.h"
48 #include "formats-util.h"
54 #include "random-util.h"
55 #include "signal-util.h"
56 #include "socket-util.h"
57 #include "string-util.h"
59 #include "terminal-util.h"
60 #include "time-util.h"
61 #include "umask-util.h"
64 #define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2)
66 static int lookup_key(const char *keyname
, key_serial_t
*ret
) {
72 serial
= request_key("user", keyname
, NULL
, 0);
74 return negative_errno();
80 static int retrieve_key(key_serial_t serial
, char ***ret
) {
81 _cleanup_free_
char *p
= NULL
;
92 n
= keyctl(KEYCTL_READ
, (unsigned long) serial
, (unsigned long) p
, (unsigned long) m
, 0);
104 l
= strv_parse_nulstr(p
, n
);
114 static int add_to_keyring(const char *keyname
, AskPasswordFlags flags
, char **passwords
) {
115 _cleanup_strv_free_erase_
char **l
= NULL
;
116 _cleanup_free_
char *p
= NULL
;
124 if (!(flags
& ASK_PASSWORD_PUSH_CACHE
))
127 r
= lookup_key(keyname
, &serial
);
129 r
= retrieve_key(serial
, &l
);
132 } else if (r
!= -ENOKEY
)
135 r
= strv_extend_strv(&l
, passwords
, true);
139 r
= strv_make_nulstr(l
, &p
, &n
);
143 /* Truncate trailing NUL */
147 serial
= add_key("user", keyname
, p
, n
-1, KEY_SPEC_USER_KEYRING
);
152 if (keyctl(KEYCTL_SET_TIMEOUT
,
153 (unsigned long) serial
,
154 (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC
, USEC_PER_SEC
), 0, 0) < 0)
155 log_debug_errno(errno
, "Failed to adjust timeout: %m");
157 log_debug("Added key to keyring as %" PRIi32
".", serial
);
162 static int add_to_keyring_and_log(const char *keyname
, AskPasswordFlags flags
, char **passwords
) {
168 r
= add_to_keyring(keyname
, flags
, passwords
);
170 return log_debug_errno(r
, "Failed to add password to keyring: %m");
175 int ask_password_keyring(const char *keyname
, AskPasswordFlags flags
, char ***ret
) {
183 if (!(flags
& ASK_PASSWORD_ACCEPT_CACHED
))
186 r
= lookup_key(keyname
, &serial
);
187 if (r
== -ENOSYS
) /* when retrieving the distinction doesn't matter */
192 return retrieve_key(serial
, ret
);
195 static void backspace_chars(int ttyfd
, size_t p
) {
203 loop_write(ttyfd
, "\b \b", 3, false);
207 int ask_password_tty(
211 AskPasswordFlags flags
,
212 const char *flag_file
,
215 struct termios old_termios
, new_termios
;
216 char passphrase
[LINE_MAX
], *x
;
219 _cleanup_close_
int ttyfd
= -1, notify
= -1;
220 struct pollfd pollfd
[2];
221 bool reset_tty
= false;
230 if (flags
& ASK_PASSWORD_NO_TTY
)
234 message
= "Password:";
237 notify
= inotify_init1(IN_CLOEXEC
|IN_NONBLOCK
);
243 if (inotify_add_watch(notify
, flag_file
, IN_ATTRIB
/* for the link count */) < 0) {
249 ttyfd
= open("/dev/tty", O_RDWR
|O_NOCTTY
|O_CLOEXEC
);
252 if (tcgetattr(ttyfd
, &old_termios
) < 0) {
257 loop_write(ttyfd
, ANSI_HIGHLIGHT
, strlen(ANSI_HIGHLIGHT
), false);
258 loop_write(ttyfd
, message
, strlen(message
), false);
259 loop_write(ttyfd
, " ", 1, false);
260 loop_write(ttyfd
, ANSI_NORMAL
, strlen(ANSI_NORMAL
), false);
262 new_termios
= old_termios
;
263 new_termios
.c_lflag
&= ~(ICANON
|ECHO
);
264 new_termios
.c_cc
[VMIN
] = 1;
265 new_termios
.c_cc
[VTIME
] = 0;
267 if (tcsetattr(ttyfd
, TCSADRAIN
, &new_termios
) < 0) {
276 pollfd
[POLL_TTY
].fd
= ttyfd
>= 0 ? ttyfd
: STDIN_FILENO
;
277 pollfd
[POLL_TTY
].events
= POLLIN
;
278 pollfd
[POLL_INOTIFY
].fd
= notify
;
279 pollfd
[POLL_INOTIFY
].events
= POLLIN
;
283 int sleep_for
= -1, k
;
289 y
= now(CLOCK_MONOTONIC
);
296 sleep_for
= (int) ((until
- y
) / USEC_PER_MSEC
);
300 if (access(flag_file
, F_OK
) < 0) {
305 k
= poll(pollfd
, notify
>= 0 ? 2 : 1, sleep_for
);
317 if (notify
>= 0 && pollfd
[POLL_INOTIFY
].revents
!= 0)
320 if (pollfd
[POLL_TTY
].revents
== 0)
323 n
= read(ttyfd
>= 0 ? ttyfd
: STDIN_FILENO
, &c
, 1);
325 if (errno
== EINTR
|| errno
== EAGAIN
)
336 else if (c
== 21) { /* C-u */
338 if (!(flags
& ASK_PASSWORD_SILENT
))
339 backspace_chars(ttyfd
, p
);
342 } else if (c
== '\b' || c
== 127) {
346 if (!(flags
& ASK_PASSWORD_SILENT
))
347 backspace_chars(ttyfd
, 1);
350 } else if (!dirty
&& !(flags
& ASK_PASSWORD_SILENT
)) {
352 flags
|= ASK_PASSWORD_SILENT
;
354 /* There are two ways to enter silent
355 * mode. Either by pressing backspace
356 * as first key (and only as first
359 loop_write(ttyfd
, "(no echo) ", 10, false);
361 } else if (ttyfd
>= 0)
362 loop_write(ttyfd
, "\a", 1, false);
364 } else if (c
== '\t' && !(flags
& ASK_PASSWORD_SILENT
)) {
366 backspace_chars(ttyfd
, p
);
367 flags
|= ASK_PASSWORD_SILENT
;
369 /* ... or by pressing TAB at any time. */
372 loop_write(ttyfd
, "(no echo) ", 10, false);
374 if (p
>= sizeof(passphrase
)-1) {
375 loop_write(ttyfd
, "\a", 1, false);
381 if (!(flags
& ASK_PASSWORD_SILENT
) && ttyfd
>= 0)
382 loop_write(ttyfd
, (flags
& ASK_PASSWORD_ECHO
) ? &c
: "*", 1, false);
390 x
= strndup(passphrase
, p
);
391 memory_erase(passphrase
, p
);
398 (void) add_to_keyring_and_log(keyname
, flags
, STRV_MAKE(x
));
404 if (ttyfd
>= 0 && reset_tty
) {
405 loop_write(ttyfd
, "\n", 1, false);
406 tcsetattr(ttyfd
, TCSADRAIN
, &old_termios
);
412 static int create_socket(char **name
) {
413 union sockaddr_union sa
= {
414 .un
.sun_family
= AF_UNIX
,
416 _cleanup_close_
int fd
= -1;
417 static const int one
= 1;
423 fd
= socket(AF_UNIX
, SOCK_DGRAM
|SOCK_CLOEXEC
|SOCK_NONBLOCK
, 0);
427 snprintf(sa
.un
.sun_path
, sizeof(sa
.un
.sun_path
)-1, "/run/systemd/ask-password/sck.%" PRIx64
, random_u64());
429 RUN_WITH_UMASK(0177) {
430 if (bind(fd
, &sa
.sa
, offsetof(struct sockaddr_un
, sun_path
) + strlen(sa
.un
.sun_path
)) < 0)
434 if (setsockopt(fd
, SOL_SOCKET
, SO_PASSCRED
, &one
, sizeof(one
)) < 0)
437 c
= strdup(sa
.un
.sun_path
);
449 int ask_password_agent(
455 AskPasswordFlags flags
,
464 _cleanup_close_
int socket_fd
= -1, signal_fd
= -1, fd
= -1;
465 char temp
[] = "/run/systemd/ask-password/tmp.XXXXXX";
466 char final
[sizeof(temp
)] = "";
467 _cleanup_free_
char *socket_name
= NULL
;
468 _cleanup_strv_free_
char **l
= NULL
;
469 _cleanup_fclose_
FILE *f
= NULL
;
470 struct pollfd pollfd
[_FD_MAX
];
471 sigset_t mask
, oldmask
;
476 if (flags
& ASK_PASSWORD_NO_AGENT
)
479 assert_se(sigemptyset(&mask
) >= 0);
480 assert_se(sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1) >= 0);
481 assert_se(sigprocmask(SIG_BLOCK
, &mask
, &oldmask
) >= 0);
483 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
485 fd
= mkostemp_safe(temp
, O_WRONLY
|O_CLOEXEC
);
491 (void) fchmod(fd
, 0644);
501 signal_fd
= signalfd(-1, &mask
, SFD_NONBLOCK
|SFD_CLOEXEC
);
507 socket_fd
= create_socket(&socket_name
);
519 "NotAfter="USEC_FMT
"\n",
522 (flags
& ASK_PASSWORD_ACCEPT_CACHED
) ? 1 : 0,
523 (flags
& ASK_PASSWORD_ECHO
) ? 1 : 0,
527 fprintf(f
, "Message=%s\n", message
);
530 fprintf(f
, "Icon=%s\n", icon
);
533 fprintf(f
, "Id=%s\n", id
);
535 r
= fflush_and_check(f
);
539 memcpy(final
, temp
, sizeof(temp
));
541 final
[sizeof(final
)-11] = 'a';
542 final
[sizeof(final
)-10] = 's';
543 final
[sizeof(final
)-9] = 'k';
545 if (rename(temp
, final
) < 0) {
551 pollfd
[FD_SOCKET
].fd
= socket_fd
;
552 pollfd
[FD_SOCKET
].events
= POLLIN
;
553 pollfd
[FD_SIGNAL
].fd
= signal_fd
;
554 pollfd
[FD_SIGNAL
].events
= POLLIN
;
557 char passphrase
[LINE_MAX
+1];
558 struct msghdr msghdr
;
562 struct cmsghdr cmsghdr
;
563 uint8_t buf
[CMSG_SPACE(sizeof(struct ucred
))];
569 t
= now(CLOCK_MONOTONIC
);
571 if (until
> 0 && until
<= t
) {
576 k
= poll(pollfd
, _FD_MAX
, until
> 0 ? (int) ((until
-t
)/USEC_PER_MSEC
) : -1);
590 if (pollfd
[FD_SIGNAL
].revents
& POLLIN
) {
595 if (pollfd
[FD_SOCKET
].revents
!= POLLIN
) {
601 iovec
.iov_base
= passphrase
;
602 iovec
.iov_len
= sizeof(passphrase
);
606 msghdr
.msg_iov
= &iovec
;
607 msghdr
.msg_iovlen
= 1;
608 msghdr
.msg_control
= &control
;
609 msghdr
.msg_controllen
= sizeof(control
);
611 n
= recvmsg(socket_fd
, &msghdr
, 0);
613 if (errno
== EAGAIN
||
621 cmsg_close_all(&msghdr
);
624 log_debug("Message too short");
628 if (msghdr
.msg_controllen
< CMSG_LEN(sizeof(struct ucred
)) ||
629 control
.cmsghdr
.cmsg_level
!= SOL_SOCKET
||
630 control
.cmsghdr
.cmsg_type
!= SCM_CREDENTIALS
||
631 control
.cmsghdr
.cmsg_len
!= CMSG_LEN(sizeof(struct ucred
))) {
632 log_debug("Received message without credentials. Ignoring.");
636 ucred
= (struct ucred
*) CMSG_DATA(&control
.cmsghdr
);
637 if (ucred
->uid
!= 0) {
638 log_debug("Got request from unprivileged user. Ignoring.");
642 if (passphrase
[0] == '+') {
643 /* An empty message refers to the empty password */
645 l
= strv_new("", NULL
);
647 l
= strv_parse_nulstr(passphrase
+1, n
-1);
648 memory_erase(passphrase
, n
);
654 if (strv_length(l
) <= 0) {
656 log_debug("Invalid packet");
663 if (passphrase
[0] == '-') {
668 log_debug("Invalid packet");
672 (void) add_to_keyring_and_log(keyname
, flags
, l
);
680 (void) unlink(socket_name
);
685 (void) unlink(final
);
687 assert_se(sigprocmask(SIG_SETMASK
, &oldmask
, NULL
) == 0);
691 int ask_password_auto(
697 AskPasswordFlags flags
,
704 if ((flags
& ASK_PASSWORD_ACCEPT_CACHED
) && keyname
) {
705 r
= ask_password_keyring(keyname
, flags
, ret
);
710 if (!(flags
& ASK_PASSWORD_NO_TTY
) && isatty(STDIN_FILENO
)) {
711 char *s
= NULL
, **l
= NULL
;
713 r
= ask_password_tty(message
, keyname
, until
, flags
, NULL
, &s
);
717 r
= strv_push(&l
, s
);
728 if (!(flags
& ASK_PASSWORD_NO_AGENT
))
729 return ask_password_agent(message
, icon
, id
, keyname
, until
, flags
, ret
);