]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/tty-ask-password-agent/tty-ask-password-agent.c
tree-wide: drop socket.h when socket-util.h is included
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 Copyright © 2015 Werner Fink
4 ***/
5
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <getopt.h>
9 #include <poll.h>
10 #include <signal.h>
11 #include <stdbool.h>
12 #include <stddef.h>
13 #include <sys/prctl.h>
14 #include <sys/signalfd.h>
15 #include <sys/stat.h>
16 #include <sys/types.h>
17 #include <sys/un.h>
18 #include <sys/wait.h>
19 #include <unistd.h>
20
21 #include "alloc-util.h"
22 #include "ask-password-api.h"
23 #include "conf-parser.h"
24 #include "def.h"
25 #include "dirent-util.h"
26 #include "exit-status.h"
27 #include "fd-util.h"
28 #include "fileio.h"
29 #include "fs-util.h"
30 #include "hashmap.h"
31 #include "io-util.h"
32 #include "macro.h"
33 #include "main-func.h"
34 #include "memory-util.h"
35 #include "mkdir.h"
36 #include "path-util.h"
37 #include "pretty-print.h"
38 #include "process-util.h"
39 #include "set.h"
40 #include "signal-util.h"
41 #include "socket-util.h"
42 #include "string-util.h"
43 #include "strv.h"
44 #include "terminal-util.h"
45 #include "utmp-wtmp.h"
46
47 static enum {
48 ACTION_LIST,
49 ACTION_QUERY,
50 ACTION_WATCH,
51 ACTION_WALL
52 } arg_action = ACTION_QUERY;
53
54 static bool arg_plymouth = false;
55 static bool arg_console = false;
56 static const char *arg_device = NULL;
57
58 static int send_passwords(const char *socket_name, char **passwords) {
59 _cleanup_(erase_and_freep) char *packet = NULL;
60 _cleanup_close_ int socket_fd = -1;
61 union sockaddr_union sa = {};
62 size_t packet_length = 1;
63 char **p, *d;
64 ssize_t n;
65 int salen;
66
67 assert(socket_name);
68
69 salen = sockaddr_un_set_path(&sa.un, socket_name);
70 if (salen < 0)
71 return salen;
72
73 STRV_FOREACH(p, passwords)
74 packet_length += strlen(*p) + 1;
75
76 packet = new(char, packet_length);
77 if (!packet)
78 return -ENOMEM;
79
80 packet[0] = '+';
81
82 d = packet + 1;
83 STRV_FOREACH(p, passwords)
84 d = stpcpy(d, *p) + 1;
85
86 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
87 if (socket_fd < 0)
88 return log_debug_errno(errno, "socket(): %m");
89
90 n = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, salen);
91 if (n < 0)
92 return log_debug_errno(errno, "sendto(): %m");
93
94 return (int) n;
95 }
96
97 static bool wall_tty_match(const char *path, void *userdata) {
98 _cleanup_free_ char *p = NULL;
99 _cleanup_close_ int fd = -1;
100 struct stat st;
101
102 if (!path_is_absolute(path))
103 path = strjoina("/dev/", path);
104
105 if (lstat(path, &st) < 0) {
106 log_debug_errno(errno, "Failed to stat %s: %m", path);
107 return true;
108 }
109
110 if (!S_ISCHR(st.st_mode)) {
111 log_debug("%s is not a character device.", path);
112 return true;
113 }
114
115 /* We use named pipes to ensure that wall messages suggesting
116 * password entry are not printed over password prompts
117 * already shown. We use the fact here that opening a pipe in
118 * non-blocking mode for write-only will succeed only if
119 * there's some writer behind it. Using pipes has the
120 * advantage that the block will automatically go away if the
121 * process dies. */
122
123 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) {
124 log_oom();
125 return true;
126 }
127
128 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
129 if (fd < 0) {
130 log_debug_errno(errno, "Failed to open the wall pipe: %m");
131 return 1;
132 }
133
134 /* What, we managed to open the pipe? Then this tty is filtered. */
135 return 0;
136 }
137
138 static int agent_ask_password_tty(
139 const char *message,
140 usec_t until,
141 AskPasswordFlags flags,
142 const char *flag_file,
143 char ***ret) {
144
145 int tty_fd = -1;
146 int r;
147
148 if (arg_console) {
149 const char *con = arg_device ?: "/dev/console";
150
151 tty_fd = acquire_terminal(con, ACQUIRE_TERMINAL_WAIT, USEC_INFINITY);
152 if (tty_fd < 0)
153 return log_error_errno(tty_fd, "Failed to acquire %s: %m", con);
154
155 r = reset_terminal_fd(tty_fd, true);
156 if (r < 0)
157 log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
158
159 }
160
161 r = ask_password_tty(tty_fd, message, NULL, until, flags, flag_file, ret);
162
163 if (arg_console) {
164 tty_fd = safe_close(tty_fd);
165 release_terminal();
166 }
167
168 return 0;
169 }
170
171 static int process_one_password_file(const char *filename) {
172 _cleanup_free_ char *socket_name = NULL, *message = NULL;
173 bool accept_cached = false, echo = false;
174 uint64_t not_after = 0;
175 unsigned pid = 0;
176
177 const ConfigTableItem items[] = {
178 { "Ask", "Socket", config_parse_string, 0, &socket_name },
179 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
180 { "Ask", "Message", config_parse_string, 0, &message },
181 { "Ask", "PID", config_parse_unsigned, 0, &pid },
182 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
183 { "Ask", "Echo", config_parse_bool, 0, &echo },
184 {}
185 };
186
187 int r;
188
189 assert(filename);
190
191 r = config_parse(NULL, filename, NULL,
192 NULL,
193 config_item_table_lookup, items,
194 CONFIG_PARSE_RELAXED|CONFIG_PARSE_WARN, NULL);
195 if (r < 0)
196 return r;
197
198 if (!socket_name)
199 return log_error_errno(SYNTHETIC_ERRNO(EBADMSG),
200 "Invalid password file %s", filename);
201
202 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
203 return 0;
204
205 if (pid > 0 && !pid_is_alive(pid))
206 return 0;
207
208 switch (arg_action) {
209 case ACTION_LIST:
210 printf("'%s' (PID %u)\n", message, pid);
211 return 0;
212
213 case ACTION_WALL: {
214 _cleanup_free_ char *wall = NULL;
215
216 if (asprintf(&wall,
217 "Password entry required for \'%s\' (PID %u).\r\n"
218 "Please enter password with the systemd-tty-ask-password-agent tool.",
219 message,
220 pid) < 0)
221 return log_oom();
222
223 (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL);
224 return 0;
225 }
226 case ACTION_QUERY:
227 case ACTION_WATCH: {
228 _cleanup_strv_free_erase_ char **passwords = NULL;
229 AskPasswordFlags flags = 0;
230
231 if (access(socket_name, W_OK) < 0) {
232 if (arg_action == ACTION_QUERY)
233 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
234
235 return 0;
236 }
237
238 SET_FLAG(flags, ASK_PASSWORD_ACCEPT_CACHED, accept_cached);
239 SET_FLAG(flags, ASK_PASSWORD_CONSOLE_COLOR, arg_console);
240 SET_FLAG(flags, ASK_PASSWORD_ECHO, echo);
241
242 if (arg_plymouth)
243 r = ask_password_plymouth(message, not_after, flags, filename, &passwords);
244 else
245 r = agent_ask_password_tty(message, not_after, flags, filename, &passwords);
246
247 if (r < 0) {
248 /* If the query went away, that's OK */
249 if (IN_SET(r, -ETIME, -ENOENT))
250 return 0;
251
252 return log_error_errno(r, "Failed to query password: %m");
253 }
254
255 if (strv_isempty(passwords))
256 return -ECANCELED;
257
258 r = send_passwords(socket_name, passwords);
259 if (r < 0)
260 return log_error_errno(r, "Failed to send: %m");
261 break;
262 }
263 }
264
265 return 0;
266 }
267
268 static int wall_tty_block(void) {
269 _cleanup_free_ char *p = NULL;
270 dev_t devnr;
271 int fd, r;
272
273 r = get_ctty_devnr(0, &devnr);
274 if (r == -ENXIO) /* We have no controlling tty */
275 return -ENOTTY;
276 if (r < 0)
277 return log_error_errno(r, "Failed to get controlling TTY: %m");
278
279 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
280 return log_oom();
281
282 (void) mkdir_parents_label(p, 0700);
283 (void) mkfifo(p, 0600);
284
285 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
286 if (fd < 0)
287 return log_debug_errno(errno, "Failed to open %s: %m", p);
288
289 return fd;
290 }
291
292 static int process_password_files(void) {
293 _cleanup_closedir_ DIR *d;
294 struct dirent *de;
295 int r = 0;
296
297 d = opendir("/run/systemd/ask-password");
298 if (!d) {
299 if (errno == ENOENT)
300 return 0;
301
302 return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m");
303 }
304
305 FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read directory: %m")) {
306 _cleanup_free_ char *p = NULL;
307 int q;
308
309 /* We only support /run on tmpfs, hence we can rely on
310 * d_type to be reliable */
311
312 if (de->d_type != DT_REG)
313 continue;
314
315 if (!startswith(de->d_name, "ask."))
316 continue;
317
318 p = path_join("/run/systemd/ask-password", de->d_name);
319 if (!p)
320 return log_oom();
321
322 q = process_one_password_file(p);
323 if (q < 0 && r == 0)
324 r = q;
325 }
326
327 return r;
328 }
329
330 static int process_and_watch_password_files(bool watch) {
331 enum {
332 FD_SIGNAL,
333 FD_INOTIFY,
334 _FD_MAX
335 };
336
337 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
338 struct pollfd pollfd[_FD_MAX];
339 sigset_t mask;
340 int r;
341
342 tty_block_fd = wall_tty_block();
343
344 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
345
346 assert_se(sigemptyset(&mask) >= 0);
347 assert_se(sigset_add_many(&mask, SIGTERM, -1) >= 0);
348 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0);
349
350 if (watch) {
351 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
352 if (signal_fd < 0)
353 return log_error_errno(errno, "Failed to allocate signal file descriptor: %m");
354
355 pollfd[FD_SIGNAL] = (struct pollfd) { .fd = signal_fd, .events = POLLIN };
356
357 notify = inotify_init1(IN_CLOEXEC);
358 if (notify < 0)
359 return log_error_errno(errno, "Failed to allocate directory watch: %m");
360
361 r = inotify_add_watch_and_warn(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO);
362 if (r < 0)
363 return r;
364
365 pollfd[FD_INOTIFY] = (struct pollfd) { .fd = notify, .events = POLLIN };
366 }
367
368 for (;;) {
369 int timeout = -1;
370
371 r = process_password_files();
372 if (r < 0) {
373 if (r == -ECANCELED)
374 /* Disable poll() timeout since at least one password has
375 * been skipped and therefore one file remains and is
376 * unlikely to trigger any events. */
377 timeout = 0;
378 else
379 /* FIXME: we should do something here since otherwise the service
380 * requesting the password won't notice the error and will wait
381 * indefinitely. */
382 log_error_errno(r, "Failed to process password: %m");
383 }
384
385 if (!watch)
386 break;
387
388 if (poll(pollfd, _FD_MAX, timeout) < 0) {
389 if (errno == EINTR)
390 continue;
391
392 return -errno;
393 }
394
395 if (pollfd[FD_INOTIFY].revents != 0)
396 (void) flush_fd(notify);
397
398 if (pollfd[FD_SIGNAL].revents != 0)
399 break;
400 }
401
402 return 0;
403 }
404
405 static int help(void) {
406 _cleanup_free_ char *link = NULL;
407 int r;
408
409 r = terminal_urlify_man("systemd-tty-ask-password-agent", "1", &link);
410 if (r < 0)
411 return log_oom();
412
413 printf("%s [OPTIONS...]\n\n"
414 "Process system password requests.\n\n"
415 " -h --help Show this help\n"
416 " --version Show package version\n"
417 " --list Show pending password requests\n"
418 " --query Process pending password requests\n"
419 " --watch Continuously process password requests\n"
420 " --wall Continuously forward password requests to wall\n"
421 " --plymouth Ask question with Plymouth instead of on TTY\n"
422 " --console Ask question on /dev/console instead of current TTY\n"
423 "\nSee the %s for details.\n"
424 , program_invocation_short_name
425 , link
426 );
427
428 return 0;
429 }
430
431 static int parse_argv(int argc, char *argv[]) {
432
433 enum {
434 ARG_LIST = 0x100,
435 ARG_QUERY,
436 ARG_WATCH,
437 ARG_WALL,
438 ARG_PLYMOUTH,
439 ARG_CONSOLE,
440 ARG_VERSION
441 };
442
443 static const struct option options[] = {
444 { "help", no_argument, NULL, 'h' },
445 { "version", no_argument, NULL, ARG_VERSION },
446 { "list", no_argument, NULL, ARG_LIST },
447 { "query", no_argument, NULL, ARG_QUERY },
448 { "watch", no_argument, NULL, ARG_WATCH },
449 { "wall", no_argument, NULL, ARG_WALL },
450 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
451 { "console", optional_argument, NULL, ARG_CONSOLE },
452 {}
453 };
454
455 int c;
456
457 assert(argc >= 0);
458 assert(argv);
459
460 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
461
462 switch (c) {
463
464 case 'h':
465 return help();
466
467 case ARG_VERSION:
468 return version();
469
470 case ARG_LIST:
471 arg_action = ACTION_LIST;
472 break;
473
474 case ARG_QUERY:
475 arg_action = ACTION_QUERY;
476 break;
477
478 case ARG_WATCH:
479 arg_action = ACTION_WATCH;
480 break;
481
482 case ARG_WALL:
483 arg_action = ACTION_WALL;
484 break;
485
486 case ARG_PLYMOUTH:
487 arg_plymouth = true;
488 break;
489
490 case ARG_CONSOLE:
491 arg_console = true;
492 if (optarg) {
493
494 if (isempty(optarg))
495 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
496 "Empty console device path is not allowed.");
497
498 arg_device = optarg;
499 }
500 break;
501
502 case '?':
503 return -EINVAL;
504
505 default:
506 assert_not_reached("Unhandled option");
507 }
508
509 if (optind != argc)
510 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
511 "%s takes no arguments.", program_invocation_short_name);
512
513 if (arg_plymouth || arg_console) {
514
515 if (!IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH))
516 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
517 "Options --query and --watch conflict.");
518
519 if (arg_plymouth && arg_console)
520 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
521 "Options --plymouth and --console conflict.");
522 }
523
524 return 1;
525 }
526
527 /*
528 * To be able to ask on all terminal devices of /dev/console the devices are collected. If more than one
529 * device is found, then on each of the terminals a inquiring task is forked. Every task has its own session
530 * and its own controlling terminal. If one of the tasks does handle a password, the remaining tasks will be
531 * terminated.
532 */
533 static int ask_on_this_console(const char *tty, pid_t *ret_pid, char **arguments) {
534 static const struct sigaction sigchld = {
535 .sa_handler = nop_signal_handler,
536 .sa_flags = SA_NOCLDSTOP | SA_RESTART,
537 };
538 static const struct sigaction sighup = {
539 .sa_handler = SIG_DFL,
540 .sa_flags = SA_RESTART,
541 };
542 int r;
543
544 assert_se(sigaction(SIGCHLD, &sigchld, NULL) >= 0);
545 assert_se(sigaction(SIGHUP, &sighup, NULL) >= 0);
546 assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD, -1) >= 0);
547
548 r = safe_fork("(sd-passwd)", FORK_RESET_SIGNALS|FORK_LOG, ret_pid);
549 if (r < 0)
550 return r;
551 if (r == 0) {
552 char **i;
553
554 assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0);
555
556 STRV_FOREACH(i, arguments) {
557 char *k;
558
559 if (!streq(*i, "--console"))
560 continue;
561
562 k = strjoin("--console=", tty);
563 if (!k) {
564 log_oom();
565 _exit(EXIT_FAILURE);
566 }
567
568 free_and_replace(*i, k);
569 }
570
571 execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, arguments);
572 _exit(EXIT_FAILURE);
573 }
574
575 return 0;
576 }
577
578 static void terminate_agents(Set *pids) {
579 struct timespec ts;
580 siginfo_t status = {};
581 sigset_t set;
582 Iterator i;
583 void *p;
584 int r, signum;
585
586 /*
587 * Request termination of the remaining processes as those
588 * are not required anymore.
589 */
590 SET_FOREACH(p, pids, i)
591 (void) kill(PTR_TO_PID(p), SIGTERM);
592
593 /*
594 * Collect the processes which have go away.
595 */
596 assert_se(sigemptyset(&set) >= 0);
597 assert_se(sigaddset(&set, SIGCHLD) >= 0);
598 timespec_store(&ts, 50 * USEC_PER_MSEC);
599
600 while (!set_isempty(pids)) {
601
602 zero(status);
603 r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
604 if (r < 0 && errno == EINTR)
605 continue;
606
607 if (r == 0 && status.si_pid > 0) {
608 set_remove(pids, PID_TO_PTR(status.si_pid));
609 continue;
610 }
611
612 signum = sigtimedwait(&set, NULL, &ts);
613 if (signum < 0) {
614 if (errno != EAGAIN)
615 log_error_errno(errno, "sigtimedwait() failed: %m");
616 break;
617 }
618 assert(signum == SIGCHLD);
619 }
620
621 /*
622 * Kill hanging processes.
623 */
624 SET_FOREACH(p, pids, i) {
625 log_warning("Failed to terminate child %d, killing it", PTR_TO_PID(p));
626 (void) kill(PTR_TO_PID(p), SIGKILL);
627 }
628 }
629
630 static int ask_on_consoles(char *argv[]) {
631 _cleanup_set_free_ Set *pids = NULL;
632 _cleanup_strv_free_ char **consoles = NULL, **arguments = NULL;
633 siginfo_t status = {};
634 char **tty;
635 pid_t pid;
636 int r;
637
638 r = get_kernel_consoles(&consoles);
639 if (r < 0)
640 return log_error_errno(r, "Failed to determine devices of /dev/console: %m");
641
642 pids = set_new(NULL);
643 if (!pids)
644 return log_oom();
645
646 arguments = strv_copy(argv);
647 if (!arguments)
648 return log_oom();
649
650 /* Start an agent on each console. */
651 STRV_FOREACH(tty, consoles) {
652 r = ask_on_this_console(*tty, &pid, arguments);
653 if (r < 0)
654 return r;
655
656 if (set_put(pids, PID_TO_PTR(pid)) < 0)
657 return log_oom();
658 }
659
660 /* Wait for an agent to exit. */
661 for (;;) {
662 zero(status);
663
664 if (waitid(P_ALL, 0, &status, WEXITED) < 0) {
665 if (errno == EINTR)
666 continue;
667
668 return log_error_errno(errno, "waitid() failed: %m");
669 }
670
671 set_remove(pids, PID_TO_PTR(status.si_pid));
672 break;
673 }
674
675 if (!is_clean_exit(status.si_code, status.si_status, EXIT_CLEAN_DAEMON, NULL))
676 log_error("Password agent failed with: %d", status.si_status);
677
678 terminate_agents(pids);
679 return 0;
680 }
681
682 static int run(int argc, char *argv[]) {
683 int r;
684
685 log_setup_service();
686
687 umask(0022);
688
689 r = parse_argv(argc, argv);
690 if (r <= 0)
691 return r;
692
693 if (arg_console && !arg_device)
694 /*
695 * Spawn a separate process for each console device.
696 */
697 return ask_on_consoles(argv);
698
699 if (arg_device) {
700 /*
701 * Later on, a controlling terminal will be acquired,
702 * therefore the current process has to become a session
703 * leader and should not have a controlling terminal already.
704 */
705 (void) setsid();
706 (void) release_terminal();
707 }
708
709 return process_and_watch_password_files(arg_action != ACTION_QUERY);
710 }
711
712 DEFINE_MAIN_FUNCTION(run);