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