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