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