]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
tty-ask-password-agent: make code a tiny bit shorter
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
ec863ba6
LP
2/***
3 This file is part of systemd.
4
5 Copyright 2010 Lennart Poettering
6af62124 6 Copyright 2015 Werner Fink
ec863ba6
LP
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
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
ec863ba6
LP
11 (at your option) any later version.
12
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
5430f7f2 16 Lesser General Public License for more details.
ec863ba6 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
ec863ba6
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
ec863ba6 22#include <errno.h>
3f6fd1ba
LP
23#include <fcntl.h>
24#include <getopt.h>
25#include <poll.h>
6af62124 26#include <signal.h>
3f6fd1ba
LP
27#include <stdbool.h>
28#include <stddef.h>
ec863ba6 29#include <string.h>
3f6fd1ba 30#include <sys/inotify.h>
6af62124 31#include <sys/prctl.h>
3f6fd1ba 32#include <sys/signalfd.h>
ec863ba6 33#include <sys/socket.h>
6af62124 34#include <sys/wait.h>
ec863ba6 35#include <sys/un.h>
ec863ba6 36#include <unistd.h>
ec863ba6 37
b5efdb8a 38#include "alloc-util.h"
3f6fd1ba
LP
39#include "ask-password-api.h"
40#include "conf-parser.h"
41#include "def.h"
a0956174 42#include "dirent-util.h"
6af62124 43#include "exit-status.h"
3ffd4af2 44#include "fd-util.h"
6af62124
WF
45#include "fileio.h"
46#include "hashmap.h"
c004493c 47#include "io-util.h"
6af62124 48#include "macro.h"
49e942b2 49#include "mkdir.h"
9eb977db 50#include "path-util.h"
3f6fd1ba
LP
51#include "process-util.h"
52#include "signal-util.h"
e5ebf783 53#include "socket-util.h"
07630cea 54#include "string-util.h"
21bc923a 55#include "strv.h"
288a74cc 56#include "terminal-util.h"
3f6fd1ba
LP
57#include "util.h"
58#include "utmp-wtmp.h"
ec863ba6
LP
59
60static enum {
61 ACTION_LIST,
62 ACTION_QUERY,
63 ACTION_WATCH,
64 ACTION_WALL
65} arg_action = ACTION_QUERY;
66
e5ebf783 67static bool arg_plymouth = false;
0cf84693 68static bool arg_console = false;
6af62124 69static const char *arg_device = NULL;
e5ebf783 70
21bc923a
LP
71static int ask_password_plymouth(
72 const char *message,
73 usec_t until,
e287086b 74 AskPasswordFlags flags,
21bc923a 75 const char *flag_file,
e287086b 76 char ***ret) {
21bc923a 77
fc2fffe7 78 static const union sockaddr_union sa = PLYMOUTH_SOCKET;
1d749d04 79 _cleanup_close_ int fd = -1, notify = -1;
1d749d04 80 _cleanup_free_ char *packet = NULL;
e5ebf783
LP
81 ssize_t k;
82 int r, n;
b92bea5d 83 struct pollfd pollfd[2] = {};
e5ebf783
LP
84 char buffer[LINE_MAX];
85 size_t p = 0;
86 enum {
87 POLL_SOCKET,
88 POLL_INOTIFY
89 };
90
e287086b 91 assert(ret);
21bc923a 92
e5ebf783 93 if (flag_file) {
1d749d04
ZJS
94 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
95 if (notify < 0)
96 return -errno;
e5ebf783 97
1d749d04
ZJS
98 r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
99 if (r < 0)
100 return -errno;
e5ebf783
LP
101 }
102
1d749d04
ZJS
103 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
104 if (fd < 0)
105 return -errno;
e5ebf783 106
fc2fffe7 107 r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
4a62c710 108 if (r < 0)
00843602 109 return -errno;
e5ebf783 110
e287086b 111 if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
21bc923a
LP
112 packet = strdup("c");
113 n = 1;
00843602 114 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
7de80bfe 115 packet = NULL;
1d749d04 116 if (!packet)
00843602 117 return -ENOMEM;
e5ebf783 118
553acb7b
ZJS
119 r = loop_write(fd, packet, n + 1, true);
120 if (r < 0)
121 return r;
e5ebf783 122
e5ebf783
LP
123 pollfd[POLL_SOCKET].fd = fd;
124 pollfd[POLL_SOCKET].events = POLLIN;
125 pollfd[POLL_INOTIFY].fd = notify;
126 pollfd[POLL_INOTIFY].events = POLLIN;
127
128 for (;;) {
129 int sleep_for = -1, j;
130
131 if (until > 0) {
132 usec_t y;
133
134 y = now(CLOCK_MONOTONIC);
135
1602b008
LP
136 if (y > until) {
137 r = -ETIME;
138 goto finish;
139 }
e5ebf783
LP
140
141 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
142 }
143
1602b008
LP
144 if (flag_file && access(flag_file, F_OK) < 0) {
145 r = -errno;
146 goto finish;
147 }
e5ebf783 148
e287086b 149 j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
1d749d04 150 if (j < 0) {
e5ebf783
LP
151 if (errno == EINTR)
152 continue;
153
1602b008
LP
154 r = -errno;
155 goto finish;
156 } else if (j == 0) {
157 r = -ETIME;
158 goto finish;
159 }
e5ebf783 160
e287086b 161 if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
665dfe93 162 (void) flush_fd(notify);
e5ebf783
LP
163
164 if (pollfd[POLL_SOCKET].revents == 0)
165 continue;
166
1d749d04 167 k = read(fd, buffer + p, sizeof(buffer) - p);
e287086b 168 if (k < 0) {
3742095b 169 if (IN_SET(errno, EINTR, EAGAIN))
e287086b
LP
170 continue;
171
1602b008
LP
172 r = -errno;
173 goto finish;
174 } else if (k == 0) {
175 r = -EIO;
176 goto finish;
177 }
e5ebf783
LP
178
179 p += k;
180
181 if (p < 1)
182 continue;
183
184 if (buffer[0] == 5) {
21bc923a 185
e287086b 186 if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
21bc923a
LP
187 /* Hmm, first try with cached
188 * passwords failed, so let's retry
189 * with a normal password request */
97b11eed 190 packet = mfree(packet);
21bc923a 191
1602b008
LP
192 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
193 r = -ENOMEM;
194 goto finish;
195 }
21bc923a 196
553acb7b
ZJS
197 r = loop_write(fd, packet, n+1, true);
198 if (r < 0)
1602b008 199 goto finish;
21bc923a 200
e287086b 201 flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
21bc923a
LP
202 p = 0;
203 continue;
204 }
205
e5ebf783 206 /* No password, because UI not shown */
1602b008
LP
207 r = -ENOENT;
208 goto finish;
e5ebf783 209
4c701096 210 } else if (IN_SET(buffer[0], 2, 9)) {
e5ebf783 211 uint32_t size;
21bc923a 212 char **l;
e5ebf783 213
4cf07da2 214 /* One or more answers */
e5ebf783
LP
215 if (p < 5)
216 continue;
217
218 memcpy(&size, buffer+1, sizeof(size));
bb53abeb 219 size = le32toh(size);
1602b008
LP
220 if (size + 5 > sizeof(buffer)) {
221 r = -EIO;
222 goto finish;
223 }
e5ebf783
LP
224
225 if (p-5 < size)
226 continue;
227
1d749d04 228 l = strv_parse_nulstr(buffer + 5, size);
1602b008
LP
229 if (!l) {
230 r = -ENOMEM;
231 goto finish;
232 }
e5ebf783 233
e287086b 234 *ret = l;
e5ebf783 235 break;
21bc923a 236
1602b008 237 } else {
e5ebf783 238 /* Unknown packet */
1602b008
LP
239 r = -EIO;
240 goto finish;
241 }
e5ebf783
LP
242 }
243
1602b008
LP
244 r = 0;
245
246finish:
2d26d8e0 247 explicit_bzero(buffer, sizeof(buffer));
1602b008 248 return r;
e5ebf783
LP
249}
250
bbada6d7
JAS
251static int send_passwords(const char *socket_name, char **passwords) {
252 _cleanup_free_ char *packet = NULL;
253 _cleanup_close_ int socket_fd = -1;
254 union sockaddr_union sa = { .un.sun_family = AF_UNIX };
255 size_t packet_length = 1;
256 char **p, *d;
257 int r;
258
259 assert(socket_name);
260
261 STRV_FOREACH(p, passwords)
262 packet_length += strlen(*p) + 1;
263
264 packet = new(char, packet_length);
265 if (!packet)
266 return -ENOMEM;
267
268 packet[0] = '+';
269
270 d = packet + 1;
271 STRV_FOREACH(p, passwords)
272 d = stpcpy(d, *p) + 1;
273
274 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
275 if (socket_fd < 0) {
276 r = log_debug_errno(errno, "socket(): %m");
277 goto finish;
278 }
279
280 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
281
fc2fffe7 282 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un));
bbada6d7
JAS
283 if (r < 0)
284 r = log_debug_errno(errno, "sendto(): %m");
285
286finish:
2d26d8e0 287 explicit_bzero(packet, packet_length);
bbada6d7
JAS
288 return r;
289}
290
0ddf1d3a 291static int parse_password(const char *filename, char **wall) {
bbada6d7 292 _cleanup_free_ char *socket_name = NULL, *message = NULL;
1602b008 293 bool accept_cached = false, echo = false;
ec863ba6
LP
294 uint64_t not_after = 0;
295 unsigned pid = 0;
ec863ba6 296
f975e971
LP
297 const ConfigTableItem items[] = {
298 { "Ask", "Socket", config_parse_string, 0, &socket_name },
299 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
300 { "Ask", "Message", config_parse_string, 0, &message },
301 { "Ask", "PID", config_parse_unsigned, 0, &pid },
302 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
64845bdc 303 { "Ask", "Echo", config_parse_bool, 0, &echo },
1d749d04 304 {}
ec863ba6
LP
305 };
306
ec863ba6 307 int r;
ec863ba6
LP
308
309 assert(filename);
310
36f822c4
ZJS
311 r = config_parse(NULL, filename, NULL,
312 NULL,
313 config_item_table_lookup, items,
bcde742e 314 CONFIG_PARSE_RELAXED|CONFIG_PARSE_WARN, NULL);
36f822c4
ZJS
315 if (r < 0)
316 return r;
ec863ba6 317
7dcda352 318 if (!socket_name) {
ec863ba6 319 log_error("Invalid password file %s", filename);
e46eab86 320 return -EBADMSG;
ec863ba6
LP
321 }
322
e46eab86
ZJS
323 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
324 return 0;
ec863ba6 325
e46eab86
ZJS
326 if (pid > 0 && !pid_is_alive(pid))
327 return 0;
ded80335 328
ec863ba6
LP
329 if (arg_action == ACTION_LIST)
330 printf("'%s' (PID %u)\n", message, pid);
e46eab86 331
ec863ba6 332 else if (arg_action == ACTION_WALL) {
0ddf1d3a 333 char *_wall;
ec863ba6 334
0ddf1d3a
LP
335 if (asprintf(&_wall,
336 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 337 "Please enter password with the systemd-tty-ask-password-agent tool!",
5cfee414 338 strempty(*wall),
0ddf1d3a 339 *wall ? "\r\n\r\n" : "",
ec863ba6 340 message,
e46eab86
ZJS
341 pid) < 0)
342 return log_oom();
ec863ba6 343
0ddf1d3a
LP
344 free(*wall);
345 *wall = _wall;
e46eab86 346
ec863ba6 347 } else {
bbada6d7 348 _cleanup_strv_free_erase_ char **passwords = NULL;
ec863ba6 349
3742095b 350 assert(IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH));
ec863ba6
LP
351
352 if (access(socket_name, W_OK) < 0) {
ec863ba6
LP
353 if (arg_action == ACTION_QUERY)
354 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
355
e46eab86 356 return 0;
ec863ba6
LP
357 }
358
bbada6d7 359 if (arg_plymouth)
e287086b 360 r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords);
bbada6d7
JAS
361 else {
362 char *password = NULL;
00843602 363 int tty_fd = -1;
0cf84693 364
e46eab86 365 if (arg_console) {
149bc84a 366 const char *con = arg_device ?: "/dev/console";
6af62124
WF
367
368 tty_fd = acquire_terminal(con, false, false, false, USEC_INFINITY);
e46eab86 369 if (tty_fd < 0)
00843602 370 return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m");
3d18b167
LP
371
372 r = reset_terminal_fd(tty_fd, true);
373 if (r < 0)
374 log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
e46eab86 375 }
0cf84693 376
e287086b 377 r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password);
e5ebf783 378
0cf84693 379 if (arg_console) {
00843602 380 tty_fd = safe_close(tty_fd);
0cf84693
LP
381 release_terminal();
382 }
21bc923a 383
bbada6d7
JAS
384 if (r >= 0)
385 r = strv_push(&passwords, password);
0cf84693 386
bbada6d7
JAS
387 if (r < 0)
388 string_free_erase(password);
1602b008 389 }
ec863ba6 390
bbada6d7
JAS
391 /* If the query went away, that's OK */
392 if (IN_SET(r, -ETIME, -ENOENT))
393 return 0;
ec863ba6 394
bbada6d7
JAS
395 if (r < 0)
396 return log_error_errno(r, "Failed to query password: %m");
ec863ba6 397
bbada6d7 398 r = send_passwords(socket_name, passwords);
00843602 399 if (r < 0)
bbada6d7 400 return log_error_errno(r, "Failed to send: %m");
ec863ba6
LP
401 }
402
e46eab86 403 return 0;
ec863ba6
LP
404}
405
0cf84693 406static int wall_tty_block(void) {
1d749d04 407 _cleanup_free_ char *p = NULL;
fc116c6a 408 dev_t devnr;
00843602 409 int fd, r;
7af53310 410
4d6d6518 411 r = get_ctty_devnr(0, &devnr);
e287086b
LP
412 if (r == -ENXIO) /* We have no controlling tty */
413 return -ENOTTY;
4d6d6518 414 if (r < 0)
00843602 415 return log_error_errno(r, "Failed to get controlling TTY: %m");
7af53310 416
2b583ce6 417 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
00843602 418 return log_oom();
7af53310 419
e3e2cf07
LP
420 (void) mkdir_parents_label(p, 0700);
421 (void) mkfifo(p, 0600);
7af53310
LP
422
423 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
7af53310 424 if (fd < 0)
2ee4e222 425 return log_debug_errno(errno, "Failed to open %s: %m", p);
7af53310
LP
426
427 return fd;
428}
429
99f710dd 430static bool wall_tty_match(const char *path, void *userdata) {
1d749d04 431 _cleanup_free_ char *p = NULL;
00843602
LP
432 _cleanup_close_ int fd = -1;
433 struct stat st;
fc116c6a 434
1d749d04 435 if (!path_is_absolute(path))
63c372cb 436 path = strjoina("/dev/", path);
fc116c6a 437
00843602
LP
438 if (lstat(path, &st) < 0) {
439 log_debug_errno(errno, "Failed to stat %s: %m", path);
fc116c6a 440 return true;
00843602 441 }
fc116c6a 442
00843602
LP
443 if (!S_ISCHR(st.st_mode)) {
444 log_debug("%s is not a character device.", path);
fc116c6a 445 return true;
00843602 446 }
7af53310
LP
447
448 /* We use named pipes to ensure that wall messages suggesting
449 * password entry are not printed over password prompts
450 * already shown. We use the fact here that opening a pipe in
451 * non-blocking mode for write-only will succeed only if
452 * there's some writer behind it. Using pipes has the
453 * advantage that the block will automatically go away if the
454 * process dies. */
455
00843602
LP
456 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) {
457 log_oom();
7af53310 458 return true;
00843602 459 }
7af53310
LP
460
461 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
00843602
LP
462 if (fd < 0) {
463 log_debug_errno(errno, "Failed top open the wall pipe: %m");
464 return 1;
465 }
7af53310
LP
466
467 /* What, we managed to open the pipe? Then this tty is filtered. */
00843602 468 return 0;
7af53310
LP
469}
470
ec863ba6 471static int show_passwords(void) {
1d749d04 472 _cleanup_closedir_ DIR *d;
ec863ba6
LP
473 struct dirent *de;
474 int r = 0;
475
1d749d04
ZJS
476 d = opendir("/run/systemd/ask-password");
477 if (!d) {
ec863ba6
LP
478 if (errno == ENOENT)
479 return 0;
480
ad71eee5 481 return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m");
ec863ba6
LP
482 }
483
00843602 484 FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) {
1d749d04 485 _cleanup_free_ char *p = NULL, *wall = NULL;
ec863ba6
LP
486 int q;
487
1a6f4df6
LP
488 /* We only support /dev on tmpfs, hence we can rely on
489 * d_type to be reliable */
490
ec863ba6
LP
491 if (de->d_type != DT_REG)
492 continue;
493
55cdd057 494 if (hidden_or_backup_file(de->d_name))
ec863ba6
LP
495 continue;
496
497 if (!startswith(de->d_name, "ask."))
498 continue;
499
1d749d04
ZJS
500 p = strappend("/run/systemd/ask-password/", de->d_name);
501 if (!p)
502 return log_oom();
ec863ba6 503
1d749d04
ZJS
504 q = parse_password(p, &wall);
505 if (q < 0 && r == 0)
ec863ba6
LP
506 r = q;
507
1d749d04 508 if (wall)
00843602 509 (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL);
ec863ba6
LP
510 }
511
ec863ba6
LP
512 return r;
513}
514
515static int watch_passwords(void) {
b9ba604e
LP
516 enum {
517 FD_INOTIFY,
518 FD_SIGNAL,
519 _FD_MAX
520 };
521
1d749d04 522 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
b92bea5d 523 struct pollfd pollfd[_FD_MAX] = {};
b9ba604e 524 sigset_t mask;
ec863ba6
LP
525 int r;
526
0cf84693 527 tty_block_fd = wall_tty_block();
7af53310 528
00843602 529 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
ec863ba6 530
1d749d04
ZJS
531 notify = inotify_init1(IN_CLOEXEC);
532 if (notify < 0)
00843602 533 return log_error_errno(errno, "Failed to allocate directory watch: %m");
ec863ba6 534
1d749d04 535 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
00843602 536 return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m");
ec863ba6 537
72c0a2c2
LP
538 assert_se(sigemptyset(&mask) >= 0);
539 assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
540 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0);
b9ba604e 541
1d749d04
ZJS
542 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
543 if (signal_fd < 0)
00843602 544 return log_error_errno(errno, "Failed to allocate signal file descriptor: %m");
b9ba604e 545
b9ba604e
LP
546 pollfd[FD_INOTIFY].fd = notify;
547 pollfd[FD_INOTIFY].events = POLLIN;
548 pollfd[FD_SIGNAL].fd = signal_fd;
549 pollfd[FD_SIGNAL].events = POLLIN;
ec863ba6
LP
550
551 for (;;) {
1d749d04
ZJS
552 r = show_passwords();
553 if (r < 0)
da927ba9 554 log_error_errno(r, "Failed to show password: %m");
ec863ba6 555
b9ba604e 556 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
557 if (errno == EINTR)
558 continue;
559
1d749d04 560 return -errno;
ec863ba6
LP
561 }
562
b9ba604e 563 if (pollfd[FD_INOTIFY].revents != 0)
00843602 564 (void) flush_fd(notify);
b9ba604e
LP
565
566 if (pollfd[FD_SIGNAL].revents != 0)
567 break;
ec863ba6
LP
568 }
569
1d749d04 570 return 0;
ec863ba6
LP
571}
572
601185b4 573static void help(void) {
ec863ba6
LP
574 printf("%s [OPTIONS...]\n\n"
575 "Process system password requests.\n\n"
e5ebf783 576 " -h --help Show this help\n"
c52f663b 577 " --version Show package version\n"
e5ebf783
LP
578 " --list Show pending password requests\n"
579 " --query Process pending password requests\n"
35b8ca3a
HH
580 " --watch Continuously process password requests\n"
581 " --wall Continuously forward password requests to wall\n"
0cf84693
LP
582 " --plymouth Ask question with Plymouth instead of on TTY\n"
583 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6 584 program_invocation_short_name);
ec863ba6
LP
585}
586
587static int parse_argv(int argc, char *argv[]) {
588
589 enum {
590 ARG_LIST = 0x100,
591 ARG_QUERY,
592 ARG_WATCH,
593 ARG_WALL,
0cf84693 594 ARG_PLYMOUTH,
c52f663b
LP
595 ARG_CONSOLE,
596 ARG_VERSION
ec863ba6
LP
597 };
598
599 static const struct option options[] = {
6af62124
WF
600 { "help", no_argument, NULL, 'h' },
601 { "version", no_argument, NULL, ARG_VERSION },
602 { "list", no_argument, NULL, ARG_LIST },
603 { "query", no_argument, NULL, ARG_QUERY },
604 { "watch", no_argument, NULL, ARG_WATCH },
605 { "wall", no_argument, NULL, ARG_WALL },
606 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
607 { "console", optional_argument, NULL, ARG_CONSOLE },
eb9da376 608 {}
ec863ba6
LP
609 };
610
611 int c;
612
613 assert(argc >= 0);
614 assert(argv);
615
601185b4 616 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
ec863ba6
LP
617
618 switch (c) {
619
620 case 'h':
601185b4
ZJS
621 help();
622 return 0;
ec863ba6 623
c52f663b 624 case ARG_VERSION:
3f6fd1ba 625 return version();
c52f663b 626
ec863ba6
LP
627 case ARG_LIST:
628 arg_action = ACTION_LIST;
629 break;
630
631 case ARG_QUERY:
632 arg_action = ACTION_QUERY;
633 break;
634
635 case ARG_WATCH:
636 arg_action = ACTION_WATCH;
637 break;
638
639 case ARG_WALL:
640 arg_action = ACTION_WALL;
641 break;
642
e5ebf783
LP
643 case ARG_PLYMOUTH:
644 arg_plymouth = true;
645 break;
646
0cf84693
LP
647 case ARG_CONSOLE:
648 arg_console = true;
6af62124
WF
649 if (optarg) {
650
651 if (isempty(optarg)) {
652 log_error("Empty console device path is not allowed.");
653 return -EINVAL;
654 }
655
656 arg_device = optarg;
657 }
0cf84693
LP
658 break;
659
ec863ba6
LP
660 case '?':
661 return -EINVAL;
662
663 default:
eb9da376 664 assert_not_reached("Unhandled option");
ec863ba6 665 }
ec863ba6
LP
666
667 if (optind != argc) {
601185b4 668 log_error("%s takes no arguments.", program_invocation_short_name);
ec863ba6
LP
669 return -EINVAL;
670 }
671
6af62124
WF
672 if (arg_plymouth || arg_console) {
673
674 if (!IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH)) {
675 log_error("Options --query and --watch conflict.");
676 return -EINVAL;
677 }
678
679 if (arg_plymouth && arg_console) {
680 log_error("Options --plymouth and --console conflict.");
681 return -EINVAL;
682 }
683 }
684
ec863ba6
LP
685 return 1;
686}
687
6af62124
WF
688/*
689 * To be able to ask on all terminal devices of /dev/console
690 * the devices are collected. If more than one device is found,
691 * then on each of the terminals a inquiring task is forked.
692 * Every task has its own session and its own controlling terminal.
693 * If one of the tasks does handle a password, the remaining tasks
694 * will be terminated.
695 */
4c253ed1 696static int ask_on_this_console(const char *tty, pid_t *ret_pid, int argc, char *argv[]) {
6af62124
WF
697 struct sigaction sig = {
698 .sa_handler = nop_signal_handler,
699 .sa_flags = SA_NOCLDSTOP | SA_RESTART,
700 };
4c253ed1
LP
701 pid_t pid;
702 int r;
6af62124
WF
703
704 assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD, -1) >= 0);
705
706 assert_se(sigemptyset(&sig.sa_mask) >= 0);
707 assert_se(sigaction(SIGCHLD, &sig, NULL) >= 0);
708
709 sig.sa_handler = SIG_DFL;
710 assert_se(sigaction(SIGHUP, &sig, NULL) >= 0);
711
b6e1fff1 712 r = safe_fork("(sd-passwd)", FORK_RESET_SIGNALS|FORK_LOG, &pid);
4c253ed1 713 if (r < 0)
b6e1fff1 714 return r;
4c253ed1 715 if (r == 0) {
6af62124
WF
716 int ac;
717
718 assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0);
719
6af62124
WF
720 for (ac = 0; ac < argc; ac++) {
721 if (streq(argv[ac], "--console")) {
421eaea0 722 argv[ac] = strjoina("--console=", tty);
6af62124
WF
723 break;
724 }
725 }
726
727 assert(ac < argc);
728
729 execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, argv);
730 _exit(EXIT_FAILURE);
731 }
4c253ed1
LP
732
733 *ret_pid = pid;
6af62124
WF
734 return 0;
735}
736
737static void terminate_agents(Set *pids) {
738 struct timespec ts;
739 siginfo_t status = {};
740 sigset_t set;
741 Iterator i;
742 void *p;
743 int r, signum;
744
745 /*
746 * Request termination of the remaining processes as those
747 * are not required anymore.
748 */
749 SET_FOREACH(p, pids, i)
750 (void) kill(PTR_TO_PID(p), SIGTERM);
751
752 /*
753 * Collect the processes which have go away.
754 */
755 assert_se(sigemptyset(&set) >= 0);
756 assert_se(sigaddset(&set, SIGCHLD) >= 0);
757 timespec_store(&ts, 50 * USEC_PER_MSEC);
758
759 while (!set_isempty(pids)) {
760
761 zero(status);
762 r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
763 if (r < 0 && errno == EINTR)
764 continue;
765
766 if (r == 0 && status.si_pid > 0) {
767 set_remove(pids, PID_TO_PTR(status.si_pid));
768 continue;
769 }
770
771 signum = sigtimedwait(&set, NULL, &ts);
772 if (signum < 0) {
773 if (errno != EAGAIN)
774 log_error_errno(errno, "sigtimedwait() failed: %m");
775 break;
776 }
777 assert(signum == SIGCHLD);
778 }
779
780 /*
781 * Kill hanging processes.
782 */
783 SET_FOREACH(p, pids, i) {
784 log_warning("Failed to terminate child %d, killing it", PTR_TO_PID(p));
785 (void) kill(PTR_TO_PID(p), SIGKILL);
786 }
787}
788
789static int ask_on_consoles(int argc, char *argv[]) {
790 _cleanup_set_free_ Set *pids = NULL;
791 _cleanup_strv_free_ char **consoles = NULL;
792 siginfo_t status = {};
793 char **tty;
794 pid_t pid;
795 int r;
796
797 r = get_kernel_consoles(&consoles);
798 if (r < 0)
799 return log_error_errno(r, "Failed to determine devices of /dev/console: %m");
800
801 pids = set_new(NULL);
802 if (!pids)
803 return log_oom();
804
805 /* Start an agent on each console. */
806 STRV_FOREACH(tty, consoles) {
807 r = ask_on_this_console(*tty, &pid, argc, argv);
808 if (r < 0)
809 return r;
810
811 if (set_put(pids, PID_TO_PTR(pid)) < 0)
812 return log_oom();
813 }
814
815 /* Wait for an agent to exit. */
816 for (;;) {
817 zero(status);
818
819 if (waitid(P_ALL, 0, &status, WEXITED) < 0) {
820 if (errno == EINTR)
821 continue;
822
823 return log_error_errno(errno, "waitid() failed: %m");
824 }
825
826 set_remove(pids, PID_TO_PTR(status.si_pid));
827 break;
828 }
829
1f0958f6 830 if (!is_clean_exit(status.si_code, status.si_status, EXIT_CLEAN_DAEMON, NULL))
6af62124
WF
831 log_error("Password agent failed with: %d", status.si_status);
832
833 terminate_agents(pids);
834 return 0;
835}
836
ec863ba6
LP
837int main(int argc, char *argv[]) {
838 int r;
839
4b261568 840 log_set_target(LOG_TARGET_AUTO);
ec863ba6
LP
841 log_parse_environment();
842 log_open();
843
4c12626c
LP
844 umask(0022);
845
1d749d04
ZJS
846 r = parse_argv(argc, argv);
847 if (r <= 0)
ec863ba6
LP
848 goto finish;
849
6af62124
WF
850 if (arg_console && !arg_device)
851 /*
852 * Spawn for each console device a separate process.
853 */
854 r = ask_on_consoles(argc, argv);
855 else {
856
857 if (arg_device) {
858 /*
859 * Later on, a controlling terminal will be acquired,
860 * therefore the current process has to become a session
861 * leader and should not have a controlling terminal already.
862 */
863 (void) setsid();
864 (void) release_terminal();
865 }
0cf84693 866
6af62124
WF
867 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
868 r = watch_passwords();
869 else
870 r = show_passwords();
871 }
96707269 872
ec863ba6
LP
873finish:
874 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
875}