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