]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
tree-wide: use IN_SET where possible
[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 167 if (k < 0) {
3742095b 168 if (IN_SET(errno, EINTR, EAGAIN))
e287086b
LP
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 348
3742095b 349 assert(IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH));
ec863ba6
LP
350
351 if (access(socket_name, W_OK) < 0) {
ec863ba6
LP
352 if (arg_action == ACTION_QUERY)
353 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
354
e46eab86 355 return 0;
ec863ba6
LP
356 }
357
bbada6d7 358 if (arg_plymouth)
e287086b 359 r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords);
bbada6d7
JAS
360 else {
361 char *password = NULL;
00843602 362 int tty_fd = -1;
0cf84693 363
e46eab86 364 if (arg_console) {
6af62124
WF
365 const char *con = arg_device ? arg_device : "/dev/console";
366
367 tty_fd = acquire_terminal(con, false, false, false, USEC_INFINITY);
e46eab86 368 if (tty_fd < 0)
00843602 369 return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m");
3d18b167
LP
370
371 r = reset_terminal_fd(tty_fd, true);
372 if (r < 0)
373 log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
e46eab86 374 }
0cf84693 375
e287086b 376 r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password);
e5ebf783 377
0cf84693 378 if (arg_console) {
00843602 379 tty_fd = safe_close(tty_fd);
0cf84693
LP
380 release_terminal();
381 }
21bc923a 382
bbada6d7
JAS
383 if (r >= 0)
384 r = strv_push(&passwords, password);
0cf84693 385
bbada6d7
JAS
386 if (r < 0)
387 string_free_erase(password);
1602b008 388 }
ec863ba6 389
bbada6d7
JAS
390 /* If the query went away, that's OK */
391 if (IN_SET(r, -ETIME, -ENOENT))
392 return 0;
ec863ba6 393
bbada6d7
JAS
394 if (r < 0)
395 return log_error_errno(r, "Failed to query password: %m");
ec863ba6 396
bbada6d7 397 r = send_passwords(socket_name, passwords);
00843602 398 if (r < 0)
bbada6d7 399 return log_error_errno(r, "Failed to send: %m");
ec863ba6
LP
400 }
401
e46eab86 402 return 0;
ec863ba6
LP
403}
404
0cf84693 405static int wall_tty_block(void) {
1d749d04 406 _cleanup_free_ char *p = NULL;
fc116c6a 407 dev_t devnr;
00843602 408 int fd, r;
7af53310 409
4d6d6518 410 r = get_ctty_devnr(0, &devnr);
e287086b
LP
411 if (r == -ENXIO) /* We have no controlling tty */
412 return -ENOTTY;
4d6d6518 413 if (r < 0)
00843602 414 return log_error_errno(r, "Failed to get controlling TTY: %m");
7af53310 415
2b583ce6 416 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
00843602 417 return log_oom();
7af53310 418
d2e54fae 419 mkdir_parents_label(p, 0700);
7af53310
LP
420 mkfifo(p, 0600);
421
422 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
7af53310 423 if (fd < 0)
2ee4e222 424 return log_debug_errno(errno, "Failed to open %s: %m", p);
7af53310
LP
425
426 return fd;
427}
428
99f710dd 429static bool wall_tty_match(const char *path, void *userdata) {
1d749d04 430 _cleanup_free_ char *p = NULL;
00843602
LP
431 _cleanup_close_ int fd = -1;
432 struct stat st;
fc116c6a 433
1d749d04 434 if (!path_is_absolute(path))
63c372cb 435 path = strjoina("/dev/", path);
fc116c6a 436
00843602
LP
437 if (lstat(path, &st) < 0) {
438 log_debug_errno(errno, "Failed to stat %s: %m", path);
fc116c6a 439 return true;
00843602 440 }
fc116c6a 441
00843602
LP
442 if (!S_ISCHR(st.st_mode)) {
443 log_debug("%s is not a character device.", path);
fc116c6a 444 return true;
00843602 445 }
7af53310
LP
446
447 /* We use named pipes to ensure that wall messages suggesting
448 * password entry are not printed over password prompts
449 * already shown. We use the fact here that opening a pipe in
450 * non-blocking mode for write-only will succeed only if
451 * there's some writer behind it. Using pipes has the
452 * advantage that the block will automatically go away if the
453 * process dies. */
454
00843602
LP
455 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) {
456 log_oom();
7af53310 457 return true;
00843602 458 }
7af53310
LP
459
460 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
00843602
LP
461 if (fd < 0) {
462 log_debug_errno(errno, "Failed top open the wall pipe: %m");
463 return 1;
464 }
7af53310
LP
465
466 /* What, we managed to open the pipe? Then this tty is filtered. */
00843602 467 return 0;
7af53310
LP
468}
469
ec863ba6 470static int show_passwords(void) {
1d749d04 471 _cleanup_closedir_ DIR *d;
ec863ba6
LP
472 struct dirent *de;
473 int r = 0;
474
1d749d04
ZJS
475 d = opendir("/run/systemd/ask-password");
476 if (!d) {
ec863ba6
LP
477 if (errno == ENOENT)
478 return 0;
479
ad71eee5 480 return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m");
ec863ba6
LP
481 }
482
00843602 483 FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) {
1d749d04 484 _cleanup_free_ char *p = NULL, *wall = NULL;
ec863ba6
LP
485 int q;
486
1a6f4df6
LP
487 /* We only support /dev on tmpfs, hence we can rely on
488 * d_type to be reliable */
489
ec863ba6
LP
490 if (de->d_type != DT_REG)
491 continue;
492
55cdd057 493 if (hidden_or_backup_file(de->d_name))
ec863ba6
LP
494 continue;
495
496 if (!startswith(de->d_name, "ask."))
497 continue;
498
1d749d04
ZJS
499 p = strappend("/run/systemd/ask-password/", de->d_name);
500 if (!p)
501 return log_oom();
ec863ba6 502
1d749d04
ZJS
503 q = parse_password(p, &wall);
504 if (q < 0 && r == 0)
ec863ba6
LP
505 r = q;
506
1d749d04 507 if (wall)
00843602 508 (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL);
ec863ba6
LP
509 }
510
ec863ba6
LP
511 return r;
512}
513
514static int watch_passwords(void) {
b9ba604e
LP
515 enum {
516 FD_INOTIFY,
517 FD_SIGNAL,
518 _FD_MAX
519 };
520
1d749d04 521 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
b92bea5d 522 struct pollfd pollfd[_FD_MAX] = {};
b9ba604e 523 sigset_t mask;
ec863ba6
LP
524 int r;
525
0cf84693 526 tty_block_fd = wall_tty_block();
7af53310 527
00843602 528 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
ec863ba6 529
1d749d04
ZJS
530 notify = inotify_init1(IN_CLOEXEC);
531 if (notify < 0)
00843602 532 return log_error_errno(errno, "Failed to allocate directory watch: %m");
ec863ba6 533
1d749d04 534 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
00843602 535 return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m");
ec863ba6 536
72c0a2c2
LP
537 assert_se(sigemptyset(&mask) >= 0);
538 assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
539 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0);
b9ba604e 540
1d749d04
ZJS
541 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
542 if (signal_fd < 0)
00843602 543 return log_error_errno(errno, "Failed to allocate signal file descriptor: %m");
b9ba604e 544
b9ba604e
LP
545 pollfd[FD_INOTIFY].fd = notify;
546 pollfd[FD_INOTIFY].events = POLLIN;
547 pollfd[FD_SIGNAL].fd = signal_fd;
548 pollfd[FD_SIGNAL].events = POLLIN;
ec863ba6
LP
549
550 for (;;) {
1d749d04
ZJS
551 r = show_passwords();
552 if (r < 0)
da927ba9 553 log_error_errno(r, "Failed to show password: %m");
ec863ba6 554
b9ba604e 555 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
556 if (errno == EINTR)
557 continue;
558
1d749d04 559 return -errno;
ec863ba6
LP
560 }
561
b9ba604e 562 if (pollfd[FD_INOTIFY].revents != 0)
00843602 563 (void) flush_fd(notify);
b9ba604e
LP
564
565 if (pollfd[FD_SIGNAL].revents != 0)
566 break;
ec863ba6
LP
567 }
568
1d749d04 569 return 0;
ec863ba6
LP
570}
571
601185b4 572static void help(void) {
ec863ba6
LP
573 printf("%s [OPTIONS...]\n\n"
574 "Process system password requests.\n\n"
e5ebf783 575 " -h --help Show this help\n"
c52f663b 576 " --version Show package version\n"
e5ebf783
LP
577 " --list Show pending password requests\n"
578 " --query Process pending password requests\n"
35b8ca3a
HH
579 " --watch Continuously process password requests\n"
580 " --wall Continuously forward password requests to wall\n"
0cf84693
LP
581 " --plymouth Ask question with Plymouth instead of on TTY\n"
582 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6 583 program_invocation_short_name);
ec863ba6
LP
584}
585
586static int parse_argv(int argc, char *argv[]) {
587
588 enum {
589 ARG_LIST = 0x100,
590 ARG_QUERY,
591 ARG_WATCH,
592 ARG_WALL,
0cf84693 593 ARG_PLYMOUTH,
c52f663b
LP
594 ARG_CONSOLE,
595 ARG_VERSION
ec863ba6
LP
596 };
597
598 static const struct option options[] = {
6af62124
WF
599 { "help", no_argument, NULL, 'h' },
600 { "version", no_argument, NULL, ARG_VERSION },
601 { "list", no_argument, NULL, ARG_LIST },
602 { "query", no_argument, NULL, ARG_QUERY },
603 { "watch", no_argument, NULL, ARG_WATCH },
604 { "wall", no_argument, NULL, ARG_WALL },
605 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
606 { "console", optional_argument, NULL, ARG_CONSOLE },
eb9da376 607 {}
ec863ba6
LP
608 };
609
610 int c;
611
612 assert(argc >= 0);
613 assert(argv);
614
601185b4 615 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
ec863ba6
LP
616
617 switch (c) {
618
619 case 'h':
601185b4
ZJS
620 help();
621 return 0;
ec863ba6 622
c52f663b 623 case ARG_VERSION:
3f6fd1ba 624 return version();
c52f663b 625
ec863ba6
LP
626 case ARG_LIST:
627 arg_action = ACTION_LIST;
628 break;
629
630 case ARG_QUERY:
631 arg_action = ACTION_QUERY;
632 break;
633
634 case ARG_WATCH:
635 arg_action = ACTION_WATCH;
636 break;
637
638 case ARG_WALL:
639 arg_action = ACTION_WALL;
640 break;
641
e5ebf783
LP
642 case ARG_PLYMOUTH:
643 arg_plymouth = true;
644 break;
645
0cf84693
LP
646 case ARG_CONSOLE:
647 arg_console = true;
6af62124
WF
648 if (optarg) {
649
650 if (isempty(optarg)) {
651 log_error("Empty console device path is not allowed.");
652 return -EINVAL;
653 }
654
655 arg_device = optarg;
656 }
0cf84693
LP
657 break;
658
ec863ba6
LP
659 case '?':
660 return -EINVAL;
661
662 default:
eb9da376 663 assert_not_reached("Unhandled option");
ec863ba6 664 }
ec863ba6
LP
665
666 if (optind != argc) {
601185b4 667 log_error("%s takes no arguments.", program_invocation_short_name);
ec863ba6
LP
668 return -EINVAL;
669 }
670
6af62124
WF
671 if (arg_plymouth || arg_console) {
672
673 if (!IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH)) {
674 log_error("Options --query and --watch conflict.");
675 return -EINVAL;
676 }
677
678 if (arg_plymouth && arg_console) {
679 log_error("Options --plymouth and --console conflict.");
680 return -EINVAL;
681 }
682 }
683
ec863ba6
LP
684 return 1;
685}
686
6af62124
WF
687/*
688 * To be able to ask on all terminal devices of /dev/console
689 * the devices are collected. If more than one device is found,
690 * then on each of the terminals a inquiring task is forked.
691 * Every task has its own session and its own controlling terminal.
692 * If one of the tasks does handle a password, the remaining tasks
693 * will be terminated.
694 */
695static int ask_on_this_console(const char *tty, pid_t *pid, int argc, char *argv[]) {
696 struct sigaction sig = {
697 .sa_handler = nop_signal_handler,
698 .sa_flags = SA_NOCLDSTOP | SA_RESTART,
699 };
700
701 assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD, -1) >= 0);
702
703 assert_se(sigemptyset(&sig.sa_mask) >= 0);
704 assert_se(sigaction(SIGCHLD, &sig, NULL) >= 0);
705
706 sig.sa_handler = SIG_DFL;
707 assert_se(sigaction(SIGHUP, &sig, NULL) >= 0);
708
709 *pid = fork();
710 if (*pid < 0)
711 return log_error_errno(errno, "Failed to fork process: %m");
712
713 if (*pid == 0) {
714 int ac;
715
716 assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0);
717
718 reset_signal_mask();
719 reset_all_signal_handlers();
720
721 for (ac = 0; ac < argc; ac++) {
722 if (streq(argv[ac], "--console")) {
723 argv[ac] = strjoina("--console=", tty, NULL);
724 break;
725 }
726 }
727
728 assert(ac < argc);
729
730 execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, argv);
731 _exit(EXIT_FAILURE);
732 }
733 return 0;
734}
735
736static void terminate_agents(Set *pids) {
737 struct timespec ts;
738 siginfo_t status = {};
739 sigset_t set;
740 Iterator i;
741 void *p;
742 int r, signum;
743
744 /*
745 * Request termination of the remaining processes as those
746 * are not required anymore.
747 */
748 SET_FOREACH(p, pids, i)
749 (void) kill(PTR_TO_PID(p), SIGTERM);
750
751 /*
752 * Collect the processes which have go away.
753 */
754 assert_se(sigemptyset(&set) >= 0);
755 assert_se(sigaddset(&set, SIGCHLD) >= 0);
756 timespec_store(&ts, 50 * USEC_PER_MSEC);
757
758 while (!set_isempty(pids)) {
759
760 zero(status);
761 r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
762 if (r < 0 && errno == EINTR)
763 continue;
764
765 if (r == 0 && status.si_pid > 0) {
766 set_remove(pids, PID_TO_PTR(status.si_pid));
767 continue;
768 }
769
770 signum = sigtimedwait(&set, NULL, &ts);
771 if (signum < 0) {
772 if (errno != EAGAIN)
773 log_error_errno(errno, "sigtimedwait() failed: %m");
774 break;
775 }
776 assert(signum == SIGCHLD);
777 }
778
779 /*
780 * Kill hanging processes.
781 */
782 SET_FOREACH(p, pids, i) {
783 log_warning("Failed to terminate child %d, killing it", PTR_TO_PID(p));
784 (void) kill(PTR_TO_PID(p), SIGKILL);
785 }
786}
787
788static int ask_on_consoles(int argc, char *argv[]) {
789 _cleanup_set_free_ Set *pids = NULL;
790 _cleanup_strv_free_ char **consoles = NULL;
791 siginfo_t status = {};
792 char **tty;
793 pid_t pid;
794 int r;
795
796 r = get_kernel_consoles(&consoles);
797 if (r < 0)
798 return log_error_errno(r, "Failed to determine devices of /dev/console: %m");
799
800 pids = set_new(NULL);
801 if (!pids)
802 return log_oom();
803
804 /* Start an agent on each console. */
805 STRV_FOREACH(tty, consoles) {
806 r = ask_on_this_console(*tty, &pid, argc, argv);
807 if (r < 0)
808 return r;
809
810 if (set_put(pids, PID_TO_PTR(pid)) < 0)
811 return log_oom();
812 }
813
814 /* Wait for an agent to exit. */
815 for (;;) {
816 zero(status);
817
818 if (waitid(P_ALL, 0, &status, WEXITED) < 0) {
819 if (errno == EINTR)
820 continue;
821
822 return log_error_errno(errno, "waitid() failed: %m");
823 }
824
825 set_remove(pids, PID_TO_PTR(status.si_pid));
826 break;
827 }
828
1f0958f6 829 if (!is_clean_exit(status.si_code, status.si_status, EXIT_CLEAN_DAEMON, NULL))
6af62124
WF
830 log_error("Password agent failed with: %d", status.si_status);
831
832 terminate_agents(pids);
833 return 0;
834}
835
ec863ba6
LP
836int main(int argc, char *argv[]) {
837 int r;
838
4b261568 839 log_set_target(LOG_TARGET_AUTO);
ec863ba6
LP
840 log_parse_environment();
841 log_open();
842
4c12626c
LP
843 umask(0022);
844
1d749d04
ZJS
845 r = parse_argv(argc, argv);
846 if (r <= 0)
ec863ba6
LP
847 goto finish;
848
6af62124
WF
849 if (arg_console && !arg_device)
850 /*
851 * Spawn for each console device a separate process.
852 */
853 r = ask_on_consoles(argc, argv);
854 else {
855
856 if (arg_device) {
857 /*
858 * Later on, a controlling terminal will be acquired,
859 * therefore the current process has to become a session
860 * leader and should not have a controlling terminal already.
861 */
862 (void) setsid();
863 (void) release_terminal();
864 }
0cf84693 865
6af62124
WF
866 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
867 r = watch_passwords();
868 else
869 r = show_passwords();
870 }
96707269 871
ec863ba6
LP
872finish:
873 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
874}