]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/tty-ask-password-agent/tty-ask-password-agent.c
30f7f42a099d4c4aec1906e0a3d8ef823fc31ab9
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <getopt.h>
25 #include <poll.h>
26 #include <stdbool.h>
27 #include <stddef.h>
28 #include <string.h>
29 #include <sys/inotify.h>
30 #include <sys/signalfd.h>
31 #include <sys/socket.h>
32 #include <sys/un.h>
33 #include <unistd.h>
34
35 #include "ask-password-api.h"
36 #include "conf-parser.h"
37 #include "def.h"
38 #include "fd-util.h"
39 #include "mkdir.h"
40 #include "path-util.h"
41 #include "process-util.h"
42 #include "signal-util.h"
43 #include "socket-util.h"
44 #include "string-util.h"
45 #include "strv.h"
46 #include "terminal-util.h"
47 #include "util.h"
48 #include "utmp-wtmp.h"
49
50 static enum {
51 ACTION_LIST,
52 ACTION_QUERY,
53 ACTION_WATCH,
54 ACTION_WALL
55 } arg_action = ACTION_QUERY;
56
57 static bool arg_plymouth = false;
58 static bool arg_console = false;
59
60 static int ask_password_plymouth(
61 const char *message,
62 usec_t until,
63 AskPasswordFlags flags,
64 const char *flag_file,
65 char ***ret) {
66
67 _cleanup_close_ int fd = -1, notify = -1;
68 union sockaddr_union sa = PLYMOUTH_SOCKET;
69 _cleanup_free_ char *packet = NULL;
70 ssize_t k;
71 int r, n;
72 struct pollfd pollfd[2] = {};
73 char buffer[LINE_MAX];
74 size_t p = 0;
75 enum {
76 POLL_SOCKET,
77 POLL_INOTIFY
78 };
79
80 assert(ret);
81
82 if (flag_file) {
83 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
84 if (notify < 0)
85 return -errno;
86
87 r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
88 if (r < 0)
89 return -errno;
90 }
91
92 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
93 if (fd < 0)
94 return -errno;
95
96 r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1));
97 if (r < 0)
98 return -errno;
99
100 if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
101 packet = strdup("c");
102 n = 1;
103 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
104 packet = NULL;
105 if (!packet)
106 return -ENOMEM;
107
108 r = loop_write(fd, packet, n + 1, true);
109 if (r < 0)
110 return r;
111
112 pollfd[POLL_SOCKET].fd = fd;
113 pollfd[POLL_SOCKET].events = POLLIN;
114 pollfd[POLL_INOTIFY].fd = notify;
115 pollfd[POLL_INOTIFY].events = POLLIN;
116
117 for (;;) {
118 int sleep_for = -1, j;
119
120 if (until > 0) {
121 usec_t y;
122
123 y = now(CLOCK_MONOTONIC);
124
125 if (y > until) {
126 r = -ETIME;
127 goto finish;
128 }
129
130 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
131 }
132
133 if (flag_file && access(flag_file, F_OK) < 0) {
134 r = -errno;
135 goto finish;
136 }
137
138 j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
139 if (j < 0) {
140 if (errno == EINTR)
141 continue;
142
143 r = -errno;
144 goto finish;
145 } else if (j == 0) {
146 r = -ETIME;
147 goto finish;
148 }
149
150 if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
151 flush_fd(notify);
152
153 if (pollfd[POLL_SOCKET].revents == 0)
154 continue;
155
156 k = read(fd, buffer + p, sizeof(buffer) - p);
157 if (k < 0) {
158 if (errno == EINTR || errno == EAGAIN)
159 continue;
160
161 r = -errno;
162 goto finish;
163 } else if (k == 0) {
164 r = -EIO;
165 goto finish;
166 }
167
168 p += k;
169
170 if (p < 1)
171 continue;
172
173 if (buffer[0] == 5) {
174
175 if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
176 /* Hmm, first try with cached
177 * passwords failed, so let's retry
178 * with a normal password request */
179 packet = mfree(packet);
180
181 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
182 r = -ENOMEM;
183 goto finish;
184 }
185
186 r = loop_write(fd, packet, n+1, true);
187 if (r < 0)
188 goto finish;
189
190 flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
191 p = 0;
192 continue;
193 }
194
195 /* No password, because UI not shown */
196 r = -ENOENT;
197 goto finish;
198
199 } else if (buffer[0] == 2 || buffer[0] == 9) {
200 uint32_t size;
201 char **l;
202
203 /* One or more answers */
204 if (p < 5)
205 continue;
206
207 memcpy(&size, buffer+1, sizeof(size));
208 size = le32toh(size);
209 if (size + 5 > sizeof(buffer)) {
210 r = -EIO;
211 goto finish;
212 }
213
214 if (p-5 < size)
215 continue;
216
217 l = strv_parse_nulstr(buffer + 5, size);
218 if (!l) {
219 r = -ENOMEM;
220 goto finish;
221 }
222
223 *ret = l;
224 break;
225
226 } else {
227 /* Unknown packet */
228 r = -EIO;
229 goto finish;
230 }
231 }
232
233 r = 0;
234
235 finish:
236 memory_erase(buffer, sizeof(buffer));
237 return r;
238 }
239
240 static int parse_password(const char *filename, char **wall) {
241 _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL;
242 bool accept_cached = false, echo = false;
243 size_t packet_length = 0;
244 uint64_t not_after = 0;
245 unsigned pid = 0;
246
247 const ConfigTableItem items[] = {
248 { "Ask", "Socket", config_parse_string, 0, &socket_name },
249 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
250 { "Ask", "Message", config_parse_string, 0, &message },
251 { "Ask", "PID", config_parse_unsigned, 0, &pid },
252 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
253 { "Ask", "Echo", config_parse_bool, 0, &echo },
254 {}
255 };
256
257 int r;
258
259 assert(filename);
260
261 r = config_parse(NULL, filename, NULL,
262 NULL,
263 config_item_table_lookup, items,
264 true, false, true, NULL);
265 if (r < 0)
266 return r;
267
268 if (!socket_name) {
269 log_error("Invalid password file %s", filename);
270 return -EBADMSG;
271 }
272
273 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
274 return 0;
275
276 if (pid > 0 && !pid_is_alive(pid))
277 return 0;
278
279 if (arg_action == ACTION_LIST)
280 printf("'%s' (PID %u)\n", message, pid);
281
282 else if (arg_action == ACTION_WALL) {
283 char *_wall;
284
285 if (asprintf(&_wall,
286 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
287 "Please enter password with the systemd-tty-ask-password-agent tool!",
288 strempty(*wall),
289 *wall ? "\r\n\r\n" : "",
290 message,
291 pid) < 0)
292 return log_oom();
293
294 free(*wall);
295 *wall = _wall;
296
297 } else {
298 union sockaddr_union sa = {};
299 _cleanup_close_ int socket_fd = -1;
300
301 assert(arg_action == ACTION_QUERY ||
302 arg_action == ACTION_WATCH);
303
304 if (access(socket_name, W_OK) < 0) {
305 if (arg_action == ACTION_QUERY)
306 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
307
308 return 0;
309 }
310
311 if (arg_plymouth) {
312 _cleanup_strv_free_erase_ char **passwords = NULL;
313
314 r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords);
315 if (r >= 0) {
316 char **p;
317
318 packet_length = 1;
319 STRV_FOREACH(p, passwords)
320 packet_length += strlen(*p) + 1;
321
322 packet = new(char, packet_length);
323 if (!packet)
324 r = -ENOMEM;
325 else {
326 char *d = packet + 1;
327
328 STRV_FOREACH(p, passwords)
329 d = stpcpy(d, *p) + 1;
330
331 packet[0] = '+';
332 }
333 }
334
335 } else {
336 _cleanup_string_free_erase_ char *password = NULL;
337 int tty_fd = -1;
338
339 if (arg_console) {
340 tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
341 if (tty_fd < 0)
342 return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m");
343
344 r = reset_terminal_fd(tty_fd, true);
345 if (r < 0)
346 log_warning_errno(r, "Failed to reset terminal, ignoring: %m");
347 }
348
349 r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password);
350
351 if (arg_console) {
352 tty_fd = safe_close(tty_fd);
353 release_terminal();
354 }
355
356 if (r >= 0) {
357 packet_length = 1 + strlen(password) + 1;
358 packet = new(char, packet_length);
359 if (!packet)
360 r = -ENOMEM;
361 else {
362 packet[0] = '+';
363 strcpy(packet + 1, password);
364 }
365 }
366 }
367
368 if (IN_SET(r, -ETIME, -ENOENT)) {
369 /* If the query went away, that's OK */
370 r = 0;
371 goto finish;
372 }
373 if (r < 0) {
374 log_error_errno(r, "Failed to query password: %m");
375 goto finish;
376 }
377
378 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
379 if (socket_fd < 0) {
380 r = log_error_errno(errno, "socket(): %m");
381 goto finish;
382 }
383
384 sa.un.sun_family = AF_UNIX;
385 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
386
387 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
388 memory_erase(packet, packet_length);
389 if (r < 0)
390 return log_error_errno(errno, "Failed to send: %m");
391 }
392
393 return 0;
394
395 finish:
396 memory_erase(packet, packet_length);
397 return r;
398 }
399
400 static int wall_tty_block(void) {
401 _cleanup_free_ char *p = NULL;
402 dev_t devnr;
403 int fd, r;
404
405 r = get_ctty_devnr(0, &devnr);
406 if (r == -ENXIO) /* We have no controlling tty */
407 return -ENOTTY;
408 if (r < 0)
409 return log_error_errno(r, "Failed to get controlling TTY: %m");
410
411 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
412 return log_oom();
413
414 mkdir_parents_label(p, 0700);
415 mkfifo(p, 0600);
416
417 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
418 if (fd < 0)
419 return log_debug_errno(errno, "Failed to open %s: %m", p);
420
421 return fd;
422 }
423
424 static bool wall_tty_match(const char *path, void *userdata) {
425 _cleanup_free_ char *p = NULL;
426 _cleanup_close_ int fd = -1;
427 struct stat st;
428
429 if (!path_is_absolute(path))
430 path = strjoina("/dev/", path);
431
432 if (lstat(path, &st) < 0) {
433 log_debug_errno(errno, "Failed to stat %s: %m", path);
434 return true;
435 }
436
437 if (!S_ISCHR(st.st_mode)) {
438 log_debug("%s is not a character device.", path);
439 return true;
440 }
441
442 /* We use named pipes to ensure that wall messages suggesting
443 * password entry are not printed over password prompts
444 * already shown. We use the fact here that opening a pipe in
445 * non-blocking mode for write-only will succeed only if
446 * there's some writer behind it. Using pipes has the
447 * advantage that the block will automatically go away if the
448 * process dies. */
449
450 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) {
451 log_oom();
452 return true;
453 }
454
455 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
456 if (fd < 0) {
457 log_debug_errno(errno, "Failed top open the wall pipe: %m");
458 return 1;
459 }
460
461 /* What, we managed to open the pipe? Then this tty is filtered. */
462 return 0;
463 }
464
465 static int show_passwords(void) {
466 _cleanup_closedir_ DIR *d;
467 struct dirent *de;
468 int r = 0;
469
470 d = opendir("/run/systemd/ask-password");
471 if (!d) {
472 if (errno == ENOENT)
473 return 0;
474
475 return log_error_errno(errno, "Failed top open /run/systemd/ask-password: %m");
476 }
477
478 FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) {
479 _cleanup_free_ char *p = NULL, *wall = NULL;
480 int q;
481
482 /* We only support /dev on tmpfs, hence we can rely on
483 * d_type to be reliable */
484
485 if (de->d_type != DT_REG)
486 continue;
487
488 if (hidden_file(de->d_name))
489 continue;
490
491 if (!startswith(de->d_name, "ask."))
492 continue;
493
494 p = strappend("/run/systemd/ask-password/", de->d_name);
495 if (!p)
496 return log_oom();
497
498 q = parse_password(p, &wall);
499 if (q < 0 && r == 0)
500 r = q;
501
502 if (wall)
503 (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL);
504 }
505
506 return r;
507 }
508
509 static int watch_passwords(void) {
510 enum {
511 FD_INOTIFY,
512 FD_SIGNAL,
513 _FD_MAX
514 };
515
516 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
517 struct pollfd pollfd[_FD_MAX] = {};
518 sigset_t mask;
519 int r;
520
521 tty_block_fd = wall_tty_block();
522
523 (void) mkdir_p_label("/run/systemd/ask-password", 0755);
524
525 notify = inotify_init1(IN_CLOEXEC);
526 if (notify < 0)
527 return log_error_errno(errno, "Failed to allocate directory watch: %m");
528
529 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
530 return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m");
531
532 assert_se(sigemptyset(&mask) >= 0);
533 assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
534 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0);
535
536 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
537 if (signal_fd < 0)
538 return log_error_errno(errno, "Failed to allocate signal file descriptor: %m");
539
540 pollfd[FD_INOTIFY].fd = notify;
541 pollfd[FD_INOTIFY].events = POLLIN;
542 pollfd[FD_SIGNAL].fd = signal_fd;
543 pollfd[FD_SIGNAL].events = POLLIN;
544
545 for (;;) {
546 r = show_passwords();
547 if (r < 0)
548 log_error_errno(r, "Failed to show password: %m");
549
550 if (poll(pollfd, _FD_MAX, -1) < 0) {
551 if (errno == EINTR)
552 continue;
553
554 return -errno;
555 }
556
557 if (pollfd[FD_INOTIFY].revents != 0)
558 (void) flush_fd(notify);
559
560 if (pollfd[FD_SIGNAL].revents != 0)
561 break;
562 }
563
564 return 0;
565 }
566
567 static void help(void) {
568 printf("%s [OPTIONS...]\n\n"
569 "Process system password requests.\n\n"
570 " -h --help Show this help\n"
571 " --version Show package version\n"
572 " --list Show pending password requests\n"
573 " --query Process pending password requests\n"
574 " --watch Continuously process password requests\n"
575 " --wall Continuously forward password requests to wall\n"
576 " --plymouth Ask question with Plymouth instead of on TTY\n"
577 " --console Ask question on /dev/console instead of current TTY\n",
578 program_invocation_short_name);
579 }
580
581 static int parse_argv(int argc, char *argv[]) {
582
583 enum {
584 ARG_LIST = 0x100,
585 ARG_QUERY,
586 ARG_WATCH,
587 ARG_WALL,
588 ARG_PLYMOUTH,
589 ARG_CONSOLE,
590 ARG_VERSION
591 };
592
593 static const struct option options[] = {
594 { "help", no_argument, NULL, 'h' },
595 { "version", no_argument, NULL, ARG_VERSION },
596 { "list", no_argument, NULL, ARG_LIST },
597 { "query", no_argument, NULL, ARG_QUERY },
598 { "watch", no_argument, NULL, ARG_WATCH },
599 { "wall", no_argument, NULL, ARG_WALL },
600 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
601 { "console", no_argument, NULL, ARG_CONSOLE },
602 {}
603 };
604
605 int c;
606
607 assert(argc >= 0);
608 assert(argv);
609
610 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
611
612 switch (c) {
613
614 case 'h':
615 help();
616 return 0;
617
618 case ARG_VERSION:
619 return version();
620
621 case ARG_LIST:
622 arg_action = ACTION_LIST;
623 break;
624
625 case ARG_QUERY:
626 arg_action = ACTION_QUERY;
627 break;
628
629 case ARG_WATCH:
630 arg_action = ACTION_WATCH;
631 break;
632
633 case ARG_WALL:
634 arg_action = ACTION_WALL;
635 break;
636
637 case ARG_PLYMOUTH:
638 arg_plymouth = true;
639 break;
640
641 case ARG_CONSOLE:
642 arg_console = true;
643 break;
644
645 case '?':
646 return -EINVAL;
647
648 default:
649 assert_not_reached("Unhandled option");
650 }
651
652 if (optind != argc) {
653 log_error("%s takes no arguments.", program_invocation_short_name);
654 return -EINVAL;
655 }
656
657 return 1;
658 }
659
660 int main(int argc, char *argv[]) {
661 int r;
662
663 log_set_target(LOG_TARGET_AUTO);
664 log_parse_environment();
665 log_open();
666
667 umask(0022);
668
669 r = parse_argv(argc, argv);
670 if (r <= 0)
671 goto finish;
672
673 if (arg_console) {
674 (void) setsid();
675 (void) release_terminal();
676 }
677
678 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
679 r = watch_passwords();
680 else
681 r = show_passwords();
682
683 finish:
684 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
685 }