]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
man: move non-target units together (#6934)
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
CommitLineData
ec863ba6
LP
1/***
2 This file is part of systemd.
3
4 Copyright 2010 Lennart Poettering
6af62124 5 Copyright 2015 Werner Fink
ec863ba6
LP
6
7 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
ec863ba6
LP
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 15 Lesser General Public License for more details.
ec863ba6 16
5430f7f2 17 You should have received a copy of the GNU Lesser General Public License
ec863ba6
LP
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19***/
20
ec863ba6 21#include <errno.h>
3f6fd1ba
LP
22#include <fcntl.h>
23#include <getopt.h>
24#include <poll.h>
6af62124 25#include <signal.h>
3f6fd1ba
LP
26#include <stdbool.h>
27#include <stddef.h>
ec863ba6 28#include <string.h>
3f6fd1ba 29#include <sys/inotify.h>
6af62124 30#include <sys/prctl.h>
3f6fd1ba 31#include <sys/signalfd.h>
ec863ba6 32#include <sys/socket.h>
6af62124 33#include <sys/wait.h>
ec863ba6 34#include <sys/un.h>
ec863ba6 35#include <unistd.h>
ec863ba6 36
b5efdb8a 37#include "alloc-util.h"
3f6fd1ba
LP
38#include "ask-password-api.h"
39#include "conf-parser.h"
40#include "def.h"
a0956174 41#include "dirent-util.h"
6af62124 42#include "exit-status.h"
3ffd4af2 43#include "fd-util.h"
6af62124
WF
44#include "fileio.h"
45#include "hashmap.h"
c004493c 46#include "io-util.h"
6af62124 47#include "macro.h"
49e942b2 48#include "mkdir.h"
9eb977db 49#include "path-util.h"
3f6fd1ba
LP
50#include "process-util.h"
51#include "signal-util.h"
e5ebf783 52#include "socket-util.h"
07630cea 53#include "string-util.h"
21bc923a 54#include "strv.h"
288a74cc 55#include "terminal-util.h"
3f6fd1ba
LP
56#include "util.h"
57#include "utmp-wtmp.h"
ec863ba6
LP
58
59static enum {
60 ACTION_LIST,
61 ACTION_QUERY,
62 ACTION_WATCH,
63 ACTION_WALL
64} arg_action = ACTION_QUERY;
65
e5ebf783 66static bool arg_plymouth = false;
0cf84693 67static bool arg_console = false;
6af62124 68static const char *arg_device = NULL;
e5ebf783 69
21bc923a
LP
70static int ask_password_plymouth(
71 const char *message,
72 usec_t until,
e287086b 73 AskPasswordFlags flags,
21bc923a 74 const char *flag_file,
e287086b 75 char ***ret) {
21bc923a 76
fc2fffe7 77 static const union sockaddr_union sa = PLYMOUTH_SOCKET;
1d749d04 78 _cleanup_close_ int fd = -1, notify = -1;
1d749d04 79 _cleanup_free_ char *packet = NULL;
e5ebf783
LP
80 ssize_t k;
81 int r, n;
b92bea5d 82 struct pollfd pollfd[2] = {};
e5ebf783
LP
83 char buffer[LINE_MAX];
84 size_t p = 0;
85 enum {
86 POLL_SOCKET,
87 POLL_INOTIFY
88 };
89
e287086b 90 assert(ret);
21bc923a 91
e5ebf783 92 if (flag_file) {
1d749d04
ZJS
93 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
94 if (notify < 0)
95 return -errno;
e5ebf783 96
1d749d04
ZJS
97 r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
98 if (r < 0)
99 return -errno;
e5ebf783
LP
100 }
101
1d749d04
ZJS
102 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
103 if (fd < 0)
104 return -errno;
e5ebf783 105
fc2fffe7 106 r = connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un));
4a62c710 107 if (r < 0)
00843602 108 return -errno;
e5ebf783 109
e287086b 110 if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
21bc923a
LP
111 packet = strdup("c");
112 n = 1;
00843602 113 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
7de80bfe 114 packet = NULL;
1d749d04 115 if (!packet)
00843602 116 return -ENOMEM;
e5ebf783 117
553acb7b
ZJS
118 r = loop_write(fd, packet, n + 1, true);
119 if (r < 0)
120 return r;
e5ebf783 121
e5ebf783
LP
122 pollfd[POLL_SOCKET].fd = fd;
123 pollfd[POLL_SOCKET].events = POLLIN;
124 pollfd[POLL_INOTIFY].fd = notify;
125 pollfd[POLL_INOTIFY].events = POLLIN;
126
127 for (;;) {
128 int sleep_for = -1, j;
129
130 if (until > 0) {
131 usec_t y;
132
133 y = now(CLOCK_MONOTONIC);
134
1602b008
LP
135 if (y > until) {
136 r = -ETIME;
137 goto finish;
138 }
e5ebf783
LP
139
140 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
141 }
142
1602b008
LP
143 if (flag_file && access(flag_file, F_OK) < 0) {
144 r = -errno;
145 goto finish;
146 }
e5ebf783 147
e287086b 148 j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
1d749d04 149 if (j < 0) {
e5ebf783
LP
150 if (errno == EINTR)
151 continue;
152
1602b008
LP
153 r = -errno;
154 goto finish;
155 } else if (j == 0) {
156 r = -ETIME;
157 goto finish;
158 }
e5ebf783 159
e287086b 160 if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
e5ebf783
LP
161 flush_fd(notify);
162
163 if (pollfd[POLL_SOCKET].revents == 0)
164 continue;
165
1d749d04 166 k = read(fd, buffer + p, sizeof(buffer) - p);
e287086b
LP
167 if (k < 0) {
168 if (errno == EINTR || errno == EAGAIN)
169 continue;
170
1602b008
LP
171 r = -errno;
172 goto finish;
173 } else if (k == 0) {
174 r = -EIO;
175 goto finish;
176 }
e5ebf783
LP
177
178 p += k;
179
180 if (p < 1)
181 continue;
182
183 if (buffer[0] == 5) {
21bc923a 184
e287086b 185 if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
21bc923a
LP
186 /* Hmm, first try with cached
187 * passwords failed, so let's retry
188 * with a normal password request */
97b11eed 189 packet = mfree(packet);
21bc923a 190
1602b008
LP
191 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
192 r = -ENOMEM;
193 goto finish;
194 }
21bc923a 195
553acb7b
ZJS
196 r = loop_write(fd, packet, n+1, true);
197 if (r < 0)
1602b008 198 goto finish;
21bc923a 199
e287086b 200 flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
21bc923a
LP
201 p = 0;
202 continue;
203 }
204
e5ebf783 205 /* No password, because UI not shown */
1602b008
LP
206 r = -ENOENT;
207 goto finish;
e5ebf783 208
21bc923a 209 } else if (buffer[0] == 2 || buffer[0] == 9) {
e5ebf783 210 uint32_t size;
21bc923a 211 char **l;
e5ebf783 212
4cf07da2 213 /* One or more answers */
e5ebf783
LP
214 if (p < 5)
215 continue;
216
217 memcpy(&size, buffer+1, sizeof(size));
bb53abeb 218 size = le32toh(size);
1602b008
LP
219 if (size + 5 > sizeof(buffer)) {
220 r = -EIO;
221 goto finish;
222 }
e5ebf783
LP
223
224 if (p-5 < size)
225 continue;
226
1d749d04 227 l = strv_parse_nulstr(buffer + 5, size);
1602b008
LP
228 if (!l) {
229 r = -ENOMEM;
230 goto finish;
231 }
e5ebf783 232
e287086b 233 *ret = l;
e5ebf783 234 break;
21bc923a 235
1602b008 236 } else {
e5ebf783 237 /* Unknown packet */
1602b008
LP
238 r = -EIO;
239 goto finish;
240 }
e5ebf783
LP
241 }
242
1602b008
LP
243 r = 0;
244
245finish:
2d26d8e0 246 explicit_bzero(buffer, sizeof(buffer));
1602b008 247 return r;
e5ebf783
LP
248}
249
bbada6d7
JAS
250static int send_passwords(const char *socket_name, char **passwords) {
251 _cleanup_free_ char *packet = NULL;
252 _cleanup_close_ int socket_fd = -1;
253 union sockaddr_union sa = { .un.sun_family = AF_UNIX };
254 size_t packet_length = 1;
255 char **p, *d;
256 int r;
257
258 assert(socket_name);
259
260 STRV_FOREACH(p, passwords)
261 packet_length += strlen(*p) + 1;
262
263 packet = new(char, packet_length);
264 if (!packet)
265 return -ENOMEM;
266
267 packet[0] = '+';
268
269 d = packet + 1;
270 STRV_FOREACH(p, passwords)
271 d = stpcpy(d, *p) + 1;
272
273 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
274 if (socket_fd < 0) {
275 r = log_debug_errno(errno, "socket(): %m");
276 goto finish;
277 }
278
279 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
280
fc2fffe7 281 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, SOCKADDR_UN_LEN(sa.un));
bbada6d7
JAS
282 if (r < 0)
283 r = log_debug_errno(errno, "sendto(): %m");
284
285finish:
2d26d8e0 286 explicit_bzero(packet, packet_length);
bbada6d7
JAS
287 return r;
288}
289
0ddf1d3a 290static int parse_password(const char *filename, char **wall) {
bbada6d7 291 _cleanup_free_ char *socket_name = NULL, *message = NULL;
1602b008 292 bool accept_cached = false, echo = false;
ec863ba6
LP
293 uint64_t not_after = 0;
294 unsigned pid = 0;
ec863ba6 295
f975e971
LP
296 const ConfigTableItem items[] = {
297 { "Ask", "Socket", config_parse_string, 0, &socket_name },
298 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
299 { "Ask", "Message", config_parse_string, 0, &message },
300 { "Ask", "PID", config_parse_unsigned, 0, &pid },
301 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
64845bdc 302 { "Ask", "Echo", config_parse_bool, 0, &echo },
1d749d04 303 {}
ec863ba6
LP
304 };
305
ec863ba6 306 int r;
ec863ba6
LP
307
308 assert(filename);
309
36f822c4
ZJS
310 r = config_parse(NULL, filename, NULL,
311 NULL,
312 config_item_table_lookup, items,
313 true, false, true, NULL);
314 if (r < 0)
315 return r;
ec863ba6 316
7dcda352 317 if (!socket_name) {
ec863ba6 318 log_error("Invalid password file %s", filename);
e46eab86 319 return -EBADMSG;
ec863ba6
LP
320 }
321
e46eab86
ZJS
322 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
323 return 0;
ec863ba6 324
e46eab86
ZJS
325 if (pid > 0 && !pid_is_alive(pid))
326 return 0;
ded80335 327
ec863ba6
LP
328 if (arg_action == ACTION_LIST)
329 printf("'%s' (PID %u)\n", message, pid);
e46eab86 330
ec863ba6 331 else if (arg_action == ACTION_WALL) {
0ddf1d3a 332 char *_wall;
ec863ba6 333
0ddf1d3a
LP
334 if (asprintf(&_wall,
335 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 336 "Please enter password with the systemd-tty-ask-password-agent tool!",
5cfee414 337 strempty(*wall),
0ddf1d3a 338 *wall ? "\r\n\r\n" : "",
ec863ba6 339 message,
e46eab86
ZJS
340 pid) < 0)
341 return log_oom();
ec863ba6 342
0ddf1d3a
LP
343 free(*wall);
344 *wall = _wall;
e46eab86 345
ec863ba6 346 } else {
bbada6d7 347 _cleanup_strv_free_erase_ char **passwords = NULL;
ec863ba6
LP
348
349 assert(arg_action == ACTION_QUERY ||
350 arg_action == ACTION_WATCH);
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) {
6af62124
WF
366 const char *con = arg_device ? arg_device : "/dev/console";
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
d2e54fae 420 mkdir_parents_label(p, 0700);
7af53310
LP
421 mkfifo(p, 0600);
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 */
696static int ask_on_this_console(const char *tty, pid_t *pid, int argc, char *argv[]) {
697 struct sigaction sig = {
698 .sa_handler = nop_signal_handler,
699 .sa_flags = SA_NOCLDSTOP | SA_RESTART,
700 };
701
702 assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD, -1) >= 0);
703
704 assert_se(sigemptyset(&sig.sa_mask) >= 0);
705 assert_se(sigaction(SIGCHLD, &sig, NULL) >= 0);
706
707 sig.sa_handler = SIG_DFL;
708 assert_se(sigaction(SIGHUP, &sig, NULL) >= 0);
709
710 *pid = fork();
711 if (*pid < 0)
712 return log_error_errno(errno, "Failed to fork process: %m");
713
714 if (*pid == 0) {
715 int ac;
716
717 assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0);
718
719 reset_signal_mask();
720 reset_all_signal_handlers();
721
722 for (ac = 0; ac < argc; ac++) {
723 if (streq(argv[ac], "--console")) {
724 argv[ac] = strjoina("--console=", tty, NULL);
725 break;
726 }
727 }
728
729 assert(ac < argc);
730
731 execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, argv);
732 _exit(EXIT_FAILURE);
733 }
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}