]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
udev: gracefully handle ENODEV or friends in opening device node
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
ec863ba6 2/***
96b2fb93 3 Copyright © 2015 Werner Fink
ec863ba6
LP
4***/
5
3f6fd1ba
LP
6#include <fcntl.h>
7#include <getopt.h>
8857aa74
DDM
8#include <poll.h>
9#include <stdlib.h>
6af62124 10#include <sys/prctl.h>
3f6fd1ba 11#include <sys/signalfd.h>
ca78ad1d 12#include <sys/stat.h>
ca78ad1d 13#include <sys/wait.h>
ec863ba6 14#include <unistd.h>
ec863ba6 15
b5efdb8a 16#include "alloc-util.h"
3f6fd1ba 17#include "ask-password-api.h"
d6b4d1c7 18#include "build.h"
3f6fd1ba 19#include "conf-parser.h"
254649d5 20#include "daemon-util.h"
eda91cf0 21#include "devnum-util.h"
a0956174 22#include "dirent-util.h"
8857aa74 23#include "errno-util.h"
6af62124 24#include "exit-status.h"
3ffd4af2 25#include "fd-util.h"
8857aa74 26#include "format-util.h"
6af62124 27#include "fileio.h"
9e5fd717 28#include "inotify-util.h"
c004493c 29#include "io-util.h"
5e332028 30#include "main-func.h"
35cd0ba5 31#include "mkdir-label.h"
9eb977db 32#include "path-util.h"
294bf0c3 33#include "pretty-print.h"
3f6fd1ba 34#include "process-util.h"
23d5dd16 35#include "set.h"
3f6fd1ba 36#include "signal-util.h"
e5ebf783 37#include "socket-util.h"
68cf1fc3 38#include "static-destruct.h"
07630cea 39#include "string-util.h"
21bc923a 40#include "strv.h"
288a74cc 41#include "terminal-util.h"
8857aa74 42#include "time-util.h"
53c0397b 43#include "wall.h"
ec863ba6
LP
44
45static enum {
46 ACTION_LIST,
47 ACTION_QUERY,
48 ACTION_WATCH,
4c4a018c 49 ACTION_WALL,
ec863ba6
LP
50} arg_action = ACTION_QUERY;
51
e5ebf783 52static bool arg_plymouth = false;
0cf84693 53static bool arg_console = false;
68cf1fc3
MY
54static char *arg_device = NULL;
55
56STATIC_DESTRUCTOR_REGISTER(arg_device, freep);
e5ebf783 57
bbada6d7 58static int send_passwords(const char *socket_name, char **passwords) {
f36a9d59 59 int r;
bbada6d7
JAS
60
61 assert(socket_name);
62
5475c3db 63 union sockaddr_union sa;
f36a9d59
ZJS
64 r = sockaddr_un_set_path(&sa.un, socket_name);
65 if (r < 0)
66 return r;
5475c3db 67 socklen_t sa_len = r;
15a3e96f 68
5475c3db 69 size_t packet_length = 1;
bbada6d7
JAS
70 STRV_FOREACH(p, passwords)
71 packet_length += strlen(*p) + 1;
72
5475c3db 73 _cleanup_(erase_and_freep) char *packet = new(char, packet_length);
bbada6d7
JAS
74 if (!packet)
75 return -ENOMEM;
76
77 packet[0] = '+';
78
5475c3db 79 char *d = packet + 1;
bbada6d7
JAS
80 STRV_FOREACH(p, passwords)
81 d = stpcpy(d, *p) + 1;
82
5475c3db 83 _cleanup_close_ int socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
e693a932
ZJS
84 if (socket_fd < 0)
85 return log_debug_errno(errno, "socket(): %m");
bbada6d7 86
5475c3db 87 ssize_t n = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, sa_len);
e693a932
ZJS
88 if (n < 0)
89 return log_debug_errno(errno, "sendto(): %m");
5439206b 90
e693a932 91 return (int) n;
bbada6d7
JAS
92}
93
16618332 94static bool wall_tty_match(const char *path, bool is_local, void *userdata) {
b5cb2d28 95 assert(path_is_absolute(path));
85c221eb 96
5475c3db 97 struct stat st;
85c221eb 98 if (lstat(path, &st) < 0) {
eda91cf0 99 log_debug_errno(errno, "Failed to stat TTY '%s', not restricting wall: %m", path);
85c221eb
FB
100 return true;
101 }
102
103 if (!S_ISCHR(st.st_mode)) {
eda91cf0 104 log_debug("TTY '%s' is not a character device, not restricting wall.", path);
85c221eb
FB
105 return true;
106 }
107
eda91cf0
LP
108 /* We use named pipes to ensure that wall messages suggesting password entry are not printed over
109 * password prompts already shown. We use the fact here that opening a pipe in non-blocking mode for
110 * write-only will succeed only if there's some writer behind it. Using pipes has the advantage that
111 * the block will automatically go away if the process dies. */
85c221eb 112
5475c3db 113 _cleanup_free_ char *p = NULL;
eda91cf0 114 if (asprintf(&p, "/run/systemd/ask-password-block/" DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(st.st_rdev)) < 0) {
5475c3db 115 log_oom_debug();
85c221eb
FB
116 return true;
117 }
118
5475c3db 119 _cleanup_close_ int fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
85c221eb 120 if (fd < 0) {
eda91cf0 121 log_debug_errno(errno, "Failed to open the wall pipe for TTY '%s', not restricting wall: %m", path);
85c221eb
FB
122 return 1;
123 }
124
125 /* What, we managed to open the pipe? Then this tty is filtered. */
126 return 0;
127}
128
87c4444a
FB
129static int agent_ask_password_tty(
130 const char *message,
131 usec_t until,
132 AskPasswordFlags flags,
133 const char *flag_file,
134 char ***ret) {
135
254d1313 136 int tty_fd = -EBADF, r;
44a989e0 137 const char *con = arg_device ?: "/dev/console";
87c4444a
FB
138
139 if (arg_console) {
789f4f7e 140 tty_fd = acquire_terminal(con, ACQUIRE_TERMINAL_WAIT|ACQUIRE_TERMINAL_WATCH_SIGTERM, USEC_INFINITY);
87c4444a
FB
141 if (tty_fd < 0)
142 return log_error_errno(tty_fd, "Failed to acquire %s: %m", con);
143
9ab703d8 144 (void) terminal_reset_defensive_locked(tty_fd, TERMINAL_RESET_SWITCH_TO_TEXT);
87c4444a 145
44a989e0 146 log_info("Starting password query on %s.", con);
87c4444a
FB
147 }
148
d08fd4c3 149 AskPasswordRequest req = {
72068d9d 150 .tty_fd = tty_fd,
d08fd4c3 151 .message = message,
4ff3689a 152 .flag_file = flag_file,
c4a02a52 153 .until = until,
d66894a7 154 .hup_fd = -EBADF,
d08fd4c3
LP
155 };
156
c4a02a52 157 r = ask_password_tty(&req, flags, ret);
87c4444a
FB
158
159 if (arg_console) {
5475c3db 160 assert(tty_fd >= 0);
87c4444a
FB
161 tty_fd = safe_close(tty_fd);
162 release_terminal();
44a989e0
ZJS
163
164 if (r >= 0)
165 log_info("Password query on %s finished successfully.", con);
87c4444a
FB
166 }
167
a4fd6cd3 168 return r;
87c4444a
FB
169}
170
5475c3db 171static int process_one_password_file(const char *filename, FILE *f) {
bbada6d7 172 _cleanup_free_ char *socket_name = NULL, *message = NULL;
1fa94a31 173 bool accept_cached = false, echo = false, silent = false;
ec863ba6 174 uint64_t not_after = 0;
65a0ede2 175 pid_t pid = 0;
ec863ba6 176
f975e971 177 const ConfigTableItem items[] = {
3f87eaa5
YW
178 { "Ask", "Socket", config_parse_string, CONFIG_PARSE_STRING_SAFE, &socket_name },
179 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
180 { "Ask", "Message", config_parse_string, 0, &message },
181 { "Ask", "PID", config_parse_pid, 0, &pid },
182 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
183 { "Ask", "Echo", config_parse_bool, 0, &echo },
184 { "Ask", "Silent", config_parse_bool, 0, &silent },
1d749d04 185 {}
ec863ba6
LP
186 };
187
ec863ba6 188 int r;
ec863ba6
LP
189
190 assert(filename);
5475c3db
LP
191 assert(f);
192
193 r = config_parse(/* unit= */ NULL,
194 filename,
195 f,
196 /* sections= */ "Ask\0",
197 config_item_table_lookup,
198 items,
4f9ff96a 199 CONFIG_PARSE_RELAXED|CONFIG_PARSE_WARN,
5475c3db
LP
200 /* userdata= */ NULL,
201 /* ret_stat= */ NULL);
36f822c4
ZJS
202 if (r < 0)
203 return r;
ec863ba6 204
baaa35ad
ZJS
205 if (!socket_name)
206 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
207 "Invalid password file %s", filename);
ec863ba6 208
e46eab86
ZJS
209 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
210 return 0;
ec863ba6 211
becdfcb9 212 if (pid > 0 && pid_is_alive(pid) <= 0)
e46eab86 213 return 0;
ded80335 214
87c4444a
FB
215 switch (arg_action) {
216 case ACTION_LIST:
65a0ede2 217 printf("'%s' (PID " PID_FMT ")\n", strna(message), pid);
87c4444a 218 return 0;
e46eab86 219
87c4444a 220 case ACTION_WALL: {
53c0397b 221 _cleanup_free_ char *msg = NULL;
ec863ba6 222
53c0397b 223 if (asprintf(&msg,
65a0ede2 224 "Password entry required for \'%s\' (PID " PID_FMT ").\r\n"
87c4444a 225 "Please enter password with the systemd-tty-ask-password-agent tool.",
66bff73b 226 strna(message),
87c4444a
FB
227 pid) < 0)
228 return log_oom();
e46eab86 229
53c0397b 230 (void) wall(msg, NULL, NULL, wall_tty_match, NULL);
87c4444a
FB
231 return 0;
232 }
233 case ACTION_QUERY:
234 case ACTION_WATCH: {
bbada6d7 235 _cleanup_strv_free_erase_ char **passwords = NULL;
87c4444a 236 AskPasswordFlags flags = 0;
ec863ba6
LP
237
238 if (access(socket_name, W_OK) < 0) {
ec863ba6 239 if (arg_action == ACTION_QUERY)
65a0ede2 240 log_info("Not querying '%s' (PID " PID_FMT "), lacking privileges.", strna(message), pid);
ec863ba6 241
e46eab86 242 return 0;
ec863ba6
LP
243 }
244
87c4444a
FB
245 SET_FLAG(flags, ASK_PASSWORD_ACCEPT_CACHED, accept_cached);
246 SET_FLAG(flags, ASK_PASSWORD_CONSOLE_COLOR, arg_console);
247 SET_FLAG(flags, ASK_PASSWORD_ECHO, echo);
1fa94a31 248 SET_FLAG(flags, ASK_PASSWORD_SILENT, silent);
3d18b167 249
49187cfc
LP
250 /* Allow providing a password via env var, for debugging purposes */
251 const char *e = secure_getenv("SYSTEMD_ASK_PASSWORD_AGENT_PASSWORD");
252 if (e) {
253 passwords = strv_new(e);
254 if (!passwords)
255 return log_oom();
256 } else {
257 if (arg_plymouth) {
258 AskPasswordRequest req = {
72068d9d 259 .tty_fd = -EBADF,
49187cfc 260 .message = message,
4ff3689a 261 .flag_file = filename,
c4a02a52 262 .until = not_after,
d66894a7 263 .hup_fd = -EBADF,
49187cfc
LP
264 };
265
c4a02a52 266 r = ask_password_plymouth(&req, flags, &passwords);
49187cfc
LP
267 } else
268 r = agent_ask_password_tty(message, not_after, flags, filename, &passwords);
269 if (r < 0) {
270 /* If the query went away, that's OK */
271 if (IN_SET(r, -ETIME, -ENOENT))
272 return 0;
273
274 return log_error_errno(r, "Failed to query password: %m");
275 }
87c4444a 276 }
ec863ba6 277
204529d0 278 assert(!strv_isempty(passwords));
bbada6d7 279 r = send_passwords(socket_name, passwords);
00843602 280 if (r < 0)
bbada6d7 281 return log_error_errno(r, "Failed to send: %m");
87c4444a 282 break;
66bff73b 283 }}
ec863ba6 284
e46eab86 285 return 0;
ec863ba6
LP
286}
287
0cf84693 288static int wall_tty_block(void) {
1d749d04 289 _cleanup_free_ char *p = NULL;
fc116c6a 290 dev_t devnr;
00843602 291 int fd, r;
7af53310 292
4d6d6518 293 r = get_ctty_devnr(0, &devnr);
e287086b
LP
294 if (r == -ENXIO) /* We have no controlling tty */
295 return -ENOTTY;
4d6d6518 296 if (r < 0)
00843602 297 return log_error_errno(r, "Failed to get controlling TTY: %m");
7af53310 298
2b583ce6 299 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
00843602 300 return log_oom();
7af53310 301
e3e2cf07
LP
302 (void) mkdir_parents_label(p, 0700);
303 (void) mkfifo(p, 0600);
7af53310
LP
304
305 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
7af53310 306 if (fd < 0)
2ee4e222 307 return log_debug_errno(errno, "Failed to open %s: %m", p);
7af53310
LP
308
309 return fd;
310}
311
ec572753 312static int process_password_files(const char *path) {
c2b2df60 313 _cleanup_closedir_ DIR *d = NULL;
5475c3db 314 int ret = 0, r;
ec863ba6 315
ec572753
LP
316 assert(path);
317
318 d = opendir(path);
1d749d04 319 if (!d) {
ec863ba6
LP
320 if (errno == ENOENT)
321 return 0;
322
ec572753 323 return log_error_errno(errno, "Failed to open '%s': %m", path);
ec863ba6
LP
324 }
325
ec572753 326 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read directory '%s': %m", path)) {
85c221eb 327 _cleanup_free_ char *p = NULL;
1a6f4df6 328
5475c3db 329 if (!IN_SET(de->d_type, DT_REG, DT_UNKNOWN))
ec863ba6
LP
330 continue;
331
ec863ba6
LP
332 if (!startswith(de->d_name, "ask."))
333 continue;
334
ec572753 335 p = path_join(path, de->d_name);
1d749d04
ZJS
336 if (!p)
337 return log_oom();
ec863ba6 338
5475c3db
LP
339 _cleanup_fclose_ FILE *f = NULL;
340 r = xfopenat(dirfd(d), de->d_name, "re", O_NOFOLLOW, &f);
341 if (r < 0) {
342 log_warning_errno(r, "Failed to open '%s', ignoring: %m", p);
343 continue;
344 }
345
346 RET_GATHER(ret, process_one_password_file(p, f));
ec863ba6
LP
347 }
348
5475c3db 349 return ret;
ec863ba6
LP
350}
351
13227570 352static int process_and_watch_password_files(bool watch) {
b9ba604e 353 enum {
b9ba604e 354 FD_SIGNAL,
13227570 355 FD_INOTIFY,
b9ba604e
LP
356 _FD_MAX
357 };
358
ec572753 359 _cleanup_free_ char *user_ask_password_directory = NULL;
254d1313 360 _unused_ _cleanup_close_ int tty_block_fd = -EBADF;
5bb1d7fb 361 _cleanup_close_ int notify = -EBADF, signal_fd = -EBADF;
7d895205 362 struct pollfd pollfd[_FD_MAX];
b9ba604e 363 sigset_t mask;
ec863ba6
LP
364 int r;
365
0cf84693 366 tty_block_fd = wall_tty_block();
7af53310 367
00843602 368 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
ec863ba6 369
ec572753
LP
370 r = acquire_user_ask_password_directory(&user_ask_password_directory);
371 if (r < 0)
372 return log_error_errno(r, "Failed to determine per-user password directory: %m");
373 if (r > 0)
374 (void) mkdir_p_label(user_ask_password_directory, 0755);
375
72c0a2c2 376 assert_se(sigemptyset(&mask) >= 0);
db7136ec 377 assert_se(sigset_add_many(&mask, SIGTERM) >= 0);
72c0a2c2 378 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0);
b9ba604e 379
13227570
FB
380 if (watch) {
381 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
382 if (signal_fd < 0)
383 return log_error_errno(errno, "Failed to allocate signal file descriptor: %m");
b9ba604e 384
7d895205 385 pollfd[FD_SIGNAL] = (struct pollfd) { .fd = signal_fd, .events = POLLIN };
13227570
FB
386
387 notify = inotify_init1(IN_CLOEXEC);
388 if (notify < 0)
389 return log_error_errno(errno, "Failed to allocate directory watch: %m");
390
ec572753 391 r = inotify_add_watch_and_warn(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO|IN_ONLYDIR);
13227570
FB
392 if (r < 0)
393 return r;
394
ec572753
LP
395 if (user_ask_password_directory) {
396 r = inotify_add_watch_and_warn(notify, user_ask_password_directory, IN_CLOSE_WRITE|IN_MOVED_TO|IN_ONLYDIR);
397 if (r < 0)
398 return r;
399 }
400
7d895205 401 pollfd[FD_INOTIFY] = (struct pollfd) { .fd = notify, .events = POLLIN };
13227570 402 }
ec863ba6 403
254649d5 404 _unused_ _cleanup_(notify_on_cleanup) const char *notify_stop =
792a8bfb 405 notify_start(NOTIFY_READY_MESSAGE, NOTIFY_STOPPING_MESSAGE);
254649d5 406
ec863ba6 407 for (;;) {
d9e2af0a 408 usec_t timeout = USEC_INFINITY;
d325f244 409
ec572753
LP
410 r = process_password_files("/run/systemd/ask-password");
411 if (user_ask_password_directory)
412 RET_GATHER(r, process_password_files(user_ask_password_directory));
5475c3db
LP
413 if (r == -ECANCELED)
414 /* Disable poll() timeout since at least one password has been skipped and therefore
415 * one file remains and is unlikely to trigger any events. */
416 timeout = 0;
417 else if (r < 0)
418 /* FIXME: we should do something here since otherwise the service
419 * requesting the password won't notice the error and will wait
420 * indefinitely. */
421 log_warning_errno(r, "Failed to process password, ignoring: %m");
ec863ba6 422
13227570
FB
423 if (!watch)
424 break;
425
d9e2af0a
YW
426 r = ppoll_usec(pollfd, _FD_MAX, timeout);
427 if (r == -EINTR)
428 continue;
429 if (r < 0)
430 return r;
dad28bff 431
b9ba604e 432 if (pollfd[FD_INOTIFY].revents != 0)
00843602 433 (void) flush_fd(notify);
b9ba604e
LP
434
435 if (pollfd[FD_SIGNAL].revents != 0)
436 break;
ec863ba6
LP
437 }
438
1d749d04 439 return 0;
ec863ba6
LP
440}
441
37ec0fdd
LP
442static int help(void) {
443 _cleanup_free_ char *link = NULL;
444 int r;
445
446 r = terminal_urlify_man("systemd-tty-ask-password-agent", "1", &link);
447 if (r < 0)
448 return log_oom();
449
ec863ba6 450 printf("%s [OPTIONS...]\n\n"
0727077b 451 "%sProcess system password requests.%s\n\n"
af88c399
ZJS
452 " -h --help Show this help\n"
453 " --version Show package version\n"
454 " --list Show pending password requests\n"
455 " --query Process pending password requests\n"
456 " --watch Continuously process password requests\n"
457 " --wall Continuously forward password requests to wall\n"
458 " --plymouth Ask question with Plymouth instead of on TTY\n"
459 " --console[=DEVICE] Ask question on /dev/console (or DEVICE if specified)\n"
460 " instead of the current TTY\n"
bc556335
DDM
461 "\nSee the %s for details.\n",
462 program_invocation_short_name,
0727077b
ZJS
463 ansi_highlight(),
464 ansi_normal(),
bc556335 465 link);
37ec0fdd
LP
466
467 return 0;
ec863ba6
LP
468}
469
470static int parse_argv(int argc, char *argv[]) {
471
472 enum {
473 ARG_LIST = 0x100,
474 ARG_QUERY,
475 ARG_WATCH,
476 ARG_WALL,
0cf84693 477 ARG_PLYMOUTH,
c52f663b
LP
478 ARG_CONSOLE,
479 ARG_VERSION
ec863ba6
LP
480 };
481
482 static const struct option options[] = {
6af62124
WF
483 { "help", no_argument, NULL, 'h' },
484 { "version", no_argument, NULL, ARG_VERSION },
485 { "list", no_argument, NULL, ARG_LIST },
486 { "query", no_argument, NULL, ARG_QUERY },
487 { "watch", no_argument, NULL, ARG_WATCH },
488 { "wall", no_argument, NULL, ARG_WALL },
489 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
490 { "console", optional_argument, NULL, ARG_CONSOLE },
eb9da376 491 {}
ec863ba6
LP
492 };
493
ce8199a1 494 int r, c;
ec863ba6
LP
495
496 assert(argc >= 0);
497 assert(argv);
498
601185b4 499 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
ec863ba6
LP
500
501 switch (c) {
502
503 case 'h':
37ec0fdd 504 return help();
ec863ba6 505
c52f663b 506 case ARG_VERSION:
3f6fd1ba 507 return version();
c52f663b 508
ec863ba6
LP
509 case ARG_LIST:
510 arg_action = ACTION_LIST;
511 break;
512
513 case ARG_QUERY:
514 arg_action = ACTION_QUERY;
515 break;
516
517 case ARG_WATCH:
518 arg_action = ACTION_WATCH;
519 break;
520
521 case ARG_WALL:
522 arg_action = ACTION_WALL;
523 break;
524
e5ebf783
LP
525 case ARG_PLYMOUTH:
526 arg_plymouth = true;
527 break;
528
0cf84693
LP
529 case ARG_CONSOLE:
530 arg_console = true;
6af62124 531 if (optarg) {
baaa35ad
ZJS
532 if (isempty(optarg))
533 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
534 "Empty console device path is not allowed.");
6af62124 535
ce8199a1
MY
536 r = free_and_strdup_warn(&arg_device, optarg);
537 if (r < 0)
538 return r;
6af62124 539 }
0cf84693
LP
540 break;
541
ec863ba6
LP
542 case '?':
543 return -EINVAL;
544
545 default:
04499a70 546 assert_not_reached();
ec863ba6 547 }
ec863ba6 548
baaa35ad
ZJS
549 if (optind != argc)
550 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
551 "%s takes no arguments.", program_invocation_short_name);
ec863ba6 552
6af62124
WF
553 if (arg_plymouth || arg_console) {
554
baaa35ad
ZJS
555 if (!IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH))
556 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
557 "Options --query and --watch conflict.");
6af62124 558
baaa35ad
ZJS
559 if (arg_plymouth && arg_console)
560 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
561 "Options --plymouth and --console conflict.");
6af62124
WF
562 }
563
ec863ba6
LP
564 return 1;
565}
566
6af62124 567/*
189b0377 568 * To be able to ask on all terminal devices of /dev/console the devices are collected. If more than one
387f6955 569 * device is found, then on each of the terminals an inquiring task is forked. Every task has its own session
c56935a0 570 * and its own controlling terminal. If one of the tasks does handle a password, the remaining tasks will be
189b0377 571 * terminated.
6af62124 572 */
c56935a0 573static int ask_on_this_console(const char *tty, char **arguments, pid_t *ret_pid) {
4c253ed1 574 int r;
6af62124 575
c56935a0
MY
576 assert(tty);
577 assert(arguments);
578 assert(ret_pid);
579
bed4386e 580 assert_se(sigaction(SIGCHLD, &sigaction_nop_nocldstop, NULL) >= 0);
0bd72176 581 assert_se(sigaction(SIGHUP, &sigaction_default, NULL) >= 0);
db7136ec 582 assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD) >= 0);
6af62124 583
4b59f6e6 584 r = safe_fork("(sd-passwd)", FORK_RESET_SIGNALS|FORK_KEEP_NOTIFY_SOCKET|FORK_LOG, ret_pid);
4c253ed1 585 if (r < 0)
b6e1fff1 586 return r;
4c253ed1 587 if (r == 0) {
6af62124
WF
588 assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0);
589
ed179fd7
LP
590 STRV_FOREACH(i, arguments) {
591 char *k;
592
593 if (!streq(*i, "--console"))
594 continue;
595
596 k = strjoin("--console=", tty);
597 if (!k) {
598 log_oom();
599 _exit(EXIT_FAILURE);
6af62124 600 }
6af62124 601
ed179fd7
LP
602 free_and_replace(*i, k);
603 }
6af62124 604
65e5d693 605 execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, arguments);
6af62124
WF
606 _exit(EXIT_FAILURE);
607 }
4c253ed1 608
6af62124
WF
609 return 0;
610}
611
612static void terminate_agents(Set *pids) {
6af62124 613 sigset_t set;
6af62124
WF
614 void *p;
615 int r, signum;
616
617 /*
618 * Request termination of the remaining processes as those
619 * are not required anymore.
620 */
90e74a66 621 SET_FOREACH(p, pids)
6af62124
WF
622 (void) kill(PTR_TO_PID(p), SIGTERM);
623
624 /*
625 * Collect the processes which have go away.
626 */
627 assert_se(sigemptyset(&set) >= 0);
628 assert_se(sigaddset(&set, SIGCHLD) >= 0);
6af62124
WF
629
630 while (!set_isempty(pids)) {
52bb308c 631 siginfo_t status = {};
6af62124 632
6af62124
WF
633 r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
634 if (r < 0 && errno == EINTR)
635 continue;
636
637 if (r == 0 && status.si_pid > 0) {
638 set_remove(pids, PID_TO_PTR(status.si_pid));
639 continue;
640 }
641
52bb308c 642 signum = sigtimedwait(&set, NULL, TIMESPEC_STORE(50 * USEC_PER_MSEC));
6af62124
WF
643 if (signum < 0) {
644 if (errno != EAGAIN)
645 log_error_errno(errno, "sigtimedwait() failed: %m");
646 break;
647 }
648 assert(signum == SIGCHLD);
649 }
650
651 /*
652 * Kill hanging processes.
653 */
90e74a66 654 SET_FOREACH(p, pids) {
6af62124
WF
655 log_warning("Failed to terminate child %d, killing it", PTR_TO_PID(p));
656 (void) kill(PTR_TO_PID(p), SIGKILL);
657 }
658}
659
ed179fd7 660static int ask_on_consoles(char *argv[]) {
6e79d2b5 661 _cleanup_strv_free_ char **consoles = NULL, **arguments = NULL;
c56935a0 662 _cleanup_set_free_ Set *pids = NULL;
6af62124
WF
663 int r;
664
59bd932a 665 assert(!arg_device);
c56935a0
MY
666 assert(argv);
667
6af62124
WF
668 r = get_kernel_consoles(&consoles);
669 if (r < 0)
670 return log_error_errno(r, "Failed to determine devices of /dev/console: %m");
68cf1fc3
MY
671 if (r <= 1) {
672 /* No need to spawn subprocesses, there's only one console or using /dev/console as fallback */
673 arg_device = TAKE_PTR(consoles[0]);
674 return 0;
675 }
6af62124
WF
676
677 pids = set_new(NULL);
678 if (!pids)
679 return log_oom();
680
6e79d2b5
YW
681 arguments = strv_copy(argv);
682 if (!arguments)
683 return log_oom();
684
4b59f6e6
MY
685 /* Grant agents we spawn notify access too, so that once an agent establishes inotify watch
686 * READY=1 from them is accepted by service manager (see process_and_watch_password_files()).
687 *
688 * Note that when any agent exits STOPPING=1 would also be sent, but that's utterly what we want,
689 * i.e. the password is answered on one console and other agents get killed below. */
690 (void) sd_notify(/* unset_environment = */ false, "NOTIFYACCESS=all");
691
6af62124
WF
692 /* Start an agent on each console. */
693 STRV_FOREACH(tty, consoles) {
c56935a0
MY
694 pid_t pid;
695
696 r = ask_on_this_console(*tty, arguments, &pid);
6af62124
WF
697 if (r < 0)
698 return r;
699
700 if (set_put(pids, PID_TO_PTR(pid)) < 0)
701 return log_oom();
702 }
703
704 /* Wait for an agent to exit. */
705 for (;;) {
c56935a0 706 siginfo_t status = {};
6af62124
WF
707
708 if (waitid(P_ALL, 0, &status, WEXITED) < 0) {
709 if (errno == EINTR)
710 continue;
711
c56935a0 712 return log_error_errno(errno, "Failed to wait for console ask-password agent: %m");
6af62124
WF
713 }
714
c56935a0
MY
715 if (!is_clean_exit(status.si_code, status.si_status, EXIT_CLEAN_DAEMON, NULL))
716 log_error("Password agent failed with: %d", status.si_status);
717
6af62124
WF
718 set_remove(pids, PID_TO_PTR(status.si_pid));
719 break;
720 }
721
6af62124 722 terminate_agents(pids);
68cf1fc3 723 return 1;
6af62124
WF
724}
725
0420d20d 726static int run(int argc, char *argv[]) {
ec863ba6
LP
727 int r;
728
d2acb93d 729 log_setup();
ec863ba6 730
4c12626c
LP
731 umask(0022);
732
1d749d04
ZJS
733 r = parse_argv(argc, argv);
734 if (r <= 0)
0420d20d 735 return r;
ec863ba6 736
68cf1fc3
MY
737 /* Spawn a separate process for each console device if there're multiple. */
738 if (arg_console && !arg_device) {
739 r = ask_on_consoles(argv);
740 if (r != 0)
741 return r;
742
743 assert(arg_device);
744 }
0cf84693 745
1de12823
MY
746 if (arg_device)
747 /* Later on, a controlling terminal will be acquired, therefore the current process has to
748 * become a session leader and should not have a controlling terminal already. */
749 terminal_detach_session();
96707269 750
4c4a018c 751 return process_and_watch_password_files(!IN_SET(arg_action, ACTION_QUERY, ACTION_LIST));
ec863ba6 752}
0420d20d
ZJS
753
754DEFINE_MAIN_FUNCTION(run);