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