]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent.c
relicense to LGPLv2.1 (with exceptions)
[thirdparty/systemd.git] / src / tty-ask-password-agent.c
CommitLineData
ec863ba6
LP
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
5430f7f2
LP
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
ec863ba6
LP
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
5430f7f2 16 Lesser General Public License for more details.
ec863ba6 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
ec863ba6
LP
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>
b9ba604e 32#include <sys/signalfd.h>
7af53310 33#include <fcntl.h>
ec863ba6
LP
34
35#include "util.h"
49e942b2 36#include "mkdir.h"
ec863ba6
LP
37#include "conf-parser.h"
38#include "utmp-wtmp.h"
e5ebf783 39#include "socket-util.h"
7f4e0805 40#include "ask-password-api.h"
21bc923a 41#include "strv.h"
ec863ba6
LP
42
43static enum {
44 ACTION_LIST,
45 ACTION_QUERY,
46 ACTION_WATCH,
47 ACTION_WALL
48} arg_action = ACTION_QUERY;
49
e5ebf783 50static bool arg_plymouth = false;
0cf84693 51static bool arg_console = false;
e5ebf783 52
21bc923a
LP
53static 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
e5ebf783
LP
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
21bc923a
LP
73 assert(_passphrases);
74
e5ebf783
LP
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;
96707269
LP
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) {
c0f9c7da 96 log_error("Failed to connect to Plymouth: %m");
e5ebf783
LP
97 r = -errno;
98 goto finish;
99 }
100
21bc923a
LP
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) {
e5ebf783
LP
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) {
ccc80078 132 r = -ETIME;
e5ebf783
LP
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) {
ccc80078 153 r = -ETIME;
e5ebf783
LP
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) {
21bc923a
LP
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
e5ebf783
LP
197 /* No password, because UI not shown */
198 r = -ENOENT;
199 goto finish;
200
21bc923a 201 } else if (buffer[0] == 2 || buffer[0] == 9) {
e5ebf783 202 uint32_t size;
21bc923a 203 char **l;
e5ebf783 204
21bc923a 205 /* One ore more answers */
e5ebf783
LP
206 if (p < 5)
207 continue;
208
209 memcpy(&size, buffer+1, sizeof(size));
bb53abeb 210 size = le32toh(size);
e5ebf783
LP
211 if (size+5 > sizeof(buffer)) {
212 r = -EIO;
213 goto finish;
214 }
215
216 if (p-5 < size)
217 continue;
218
21bc923a 219 if (!(l = strv_parse_nulstr(buffer + 5, size))) {
e5ebf783
LP
220 r = -ENOMEM;
221 goto finish;
222 }
223
21bc923a 224 *_passphrases = l;
e5ebf783 225 break;
21bc923a 226
e5ebf783
LP
227 } else {
228 /* Unknown packet */
229 r = -EIO;
230 goto finish;
231 }
232 }
233
234 r = 0;
235
236finish:
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
0ddf1d3a 248static int parse_password(const char *filename, char **wall) {
ec863ba6
LP
249 char *socket_name = NULL, *message = NULL, *packet = NULL;
250 uint64_t not_after = 0;
251 unsigned pid = 0;
252 int socket_fd = -1;
21bc923a 253 bool accept_cached = false;
ec863ba6 254
f975e971
LP
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 }
ec863ba6
LP
262 };
263
264 FILE *f;
265 int r;
ec863ba6
LP
266
267 assert(filename);
268
f975e971
LP
269 f = fopen(filename, "re");
270 if (!f) {
ec863ba6
LP
271 if (errno == ENOENT)
272 return 0;
273
274 log_error("open(%s): %m", filename);
275 return -errno;
276 }
277
f975e971
LP
278 r = config_parse(filename, f, NULL, config_item_table_lookup, (void*) items, true, NULL);
279 if (r < 0) {
ec863ba6
LP
280 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
281 goto finish;
282 }
283
7dcda352 284 if (!socket_name) {
ec863ba6
LP
285 log_error("Invalid password file %s", filename);
286 r = -EBADMSG;
287 goto finish;
288 }
289
7dcda352
LP
290 if (not_after > 0) {
291 if (now(CLOCK_MONOTONIC) > not_after) {
292 r = 0;
293 goto finish;
294 }
ec863ba6
LP
295 }
296
ded80335
LP
297 if (pid > 0 &&
298 kill(pid, 0) < 0 &&
299 errno == ESRCH) {
300 r = 0;
301 goto finish;
302 }
303
ec863ba6
LP
304 if (arg_action == ACTION_LIST)
305 printf("'%s' (PID %u)\n", message, pid);
306 else if (arg_action == ACTION_WALL) {
0ddf1d3a 307 char *_wall;
ec863ba6 308
0ddf1d3a
LP
309 if (asprintf(&_wall,
310 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 311 "Please enter password with the systemd-tty-ask-password-agent tool!",
0ddf1d3a
LP
312 *wall ? *wall : "",
313 *wall ? "\r\n\r\n" : "",
ec863ba6
LP
314 message,
315 pid) < 0) {
316 log_error("Out of memory");
317 r = -ENOMEM;
318 goto finish;
319 }
320
0ddf1d3a
LP
321 free(*wall);
322 *wall = _wall;
ec863ba6
LP
323 } else {
324 union {
325 struct sockaddr sa;
326 struct sockaddr_un un;
327 } sa;
3414abee 328 size_t packet_length = 0;
ec863ba6
LP
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
21bc923a 342 if (arg_plymouth) {
3414abee 343 char **passwords = NULL;
21bc923a
LP
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 {
0cf84693 366 int tty_fd = -1;
21bc923a 367 char *password;
0cf84693
LP
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
e5ebf783
LP
375 r = ask_password_tty(message, not_after, filename, &password);
376
0cf84693
LP
377 if (arg_console) {
378 close_nointr_nofail(tty_fd);
379 release_terminal();
380 }
21bc923a 381
9726f9ff
LP
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 }
21bc923a 390
5ba7b871
LP
391 free(password);
392 }
0cf84693
LP
393 }
394
446f0046
LP
395 if (r == -ETIME || r == -ENOENT) {
396 /* If the query went away, that's OK */
397 r = 0;
398 goto finish;
399 }
400
e5ebf783
LP
401 if (r < 0) {
402 log_error("Failed to query password: %s", strerror(-r));
ec863ba6
LP
403 goto finish;
404 }
405
ec863ba6
LP
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
21bc923a 416 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
ec863ba6
LP
417 log_error("Failed to send: %m");
418 r = -errno;
419 goto finish;
420 }
421 }
422
423finish:
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
0cf84693 436static int wall_tty_block(void) {
7af53310 437 char *p;
fc116c6a
LP
438 int fd, r;
439 dev_t devnr;
7af53310 440
4d6d6518
LP
441 r = get_ctty_devnr(0, &devnr);
442 if (r < 0)
fc116c6a 443 return -r;
7af53310 444
2b583ce6 445 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
7af53310
LP
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
0cf84693 460static bool wall_tty_match(const char *path) {
fc116c6a 461 int fd, k;
7af53310 462 char *p;
fc116c6a
LP
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;
7af53310
LP
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
2b583ce6 489 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
7af53310
LP
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
ec863ba6
LP
503static int show_passwords(void) {
504 DIR *d;
505 struct dirent *de;
506 int r = 0;
507
2b583ce6 508 if (!(d = opendir("/run/systemd/ask-password"))) {
ec863ba6
LP
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;
0ddf1d3a 519 char *wall;
ec863ba6 520
1a6f4df6
LP
521 /* We only support /dev on tmpfs, hence we can rely on
522 * d_type to be reliable */
523
ec863ba6
LP
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
2b583ce6 533 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
ec863ba6
LP
534 log_error("Out of memory");
535 r = -ENOMEM;
536 goto finish;
537 }
538
0ddf1d3a
LP
539 wall = NULL;
540 if ((q = parse_password(p, &wall)) < 0)
ec863ba6
LP
541 r = q;
542
543 free(p);
0ddf1d3a
LP
544
545 if (wall) {
0cf84693 546 utmp_wall(wall, wall_tty_match);
0ddf1d3a
LP
547 free(wall);
548 }
ec863ba6
LP
549 }
550
551finish:
552 if (d)
553 closedir(d);
554
555 return r;
556}
557
558static int watch_passwords(void) {
b9ba604e
LP
559 enum {
560 FD_INOTIFY,
561 FD_SIGNAL,
562 _FD_MAX
563 };
564
7af53310 565 int notify = -1, signal_fd = -1, tty_block_fd = -1;
b9ba604e
LP
566 struct pollfd pollfd[_FD_MAX];
567 sigset_t mask;
ec863ba6
LP
568 int r;
569
0cf84693 570 tty_block_fd = wall_tty_block();
7af53310 571
2b583ce6 572 mkdir_p("/run/systemd/ask-password", 0755);
ec863ba6
LP
573
574 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
575 r = -errno;
576 goto finish;
577 }
578
2b583ce6 579 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
ec863ba6
LP
580 r = -errno;
581 goto finish;
582 }
583
b9ba604e
LP
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
ec863ba6 594 zero(pollfd);
b9ba604e
LP
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;
ec863ba6
LP
599
600 for (;;) {
601 if ((r = show_passwords()) < 0)
0cf84693 602 log_error("Failed to show password: %s", strerror(-r));
ec863ba6 603
b9ba604e 604 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
605
606 if (errno == EINTR)
607 continue;
608
609 r = -errno;
610 goto finish;
611 }
612
b9ba604e 613 if (pollfd[FD_INOTIFY].revents != 0)
ec863ba6 614 flush_fd(notify);
b9ba604e
LP
615
616 if (pollfd[FD_SIGNAL].revents != 0)
617 break;
ec863ba6
LP
618 }
619
620 r = 0;
621
622finish:
623 if (notify >= 0)
624 close_nointr_nofail(notify);
625
b9ba604e
LP
626 if (signal_fd >= 0)
627 close_nointr_nofail(signal_fd);
628
7af53310
LP
629 if (tty_block_fd >= 0)
630 close_nointr_nofail(tty_block_fd);
631
ec863ba6
LP
632 return r;
633}
634
635static int help(void) {
636
637 printf("%s [OPTIONS...]\n\n"
638 "Process system password requests.\n\n"
e5ebf783
LP
639 " -h --help Show this help\n"
640 " --list Show pending password requests\n"
641 " --query Process pending password requests\n"
35b8ca3a
HH
642 " --watch Continuously process password requests\n"
643 " --wall Continuously forward password requests to wall\n"
0cf84693
LP
644 " --plymouth Ask question with Plymouth instead of on TTY\n"
645 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6
LP
646 program_invocation_short_name);
647
648 return 0;
649}
650
651static int parse_argv(int argc, char *argv[]) {
652
653 enum {
654 ARG_LIST = 0x100,
655 ARG_QUERY,
656 ARG_WATCH,
657 ARG_WALL,
0cf84693
LP
658 ARG_PLYMOUTH,
659 ARG_CONSOLE
ec863ba6
LP
660 };
661
662 static const struct option options[] = {
e5ebf783
LP
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 },
0cf84693 669 { "console", no_argument, NULL, ARG_CONSOLE },
e5ebf783 670 { NULL, 0, NULL, 0 }
ec863ba6
LP
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
e5ebf783
LP
702 case ARG_PLYMOUTH:
703 arg_plymouth = true;
704 break;
705
0cf84693
LP
706 case ARG_CONSOLE:
707 arg_console = true;
708 break;
709
ec863ba6
LP
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
727int main(int argc, char *argv[]) {
728 int r;
729
730 log_parse_environment();
731 log_open();
732
4c12626c
LP
733 umask(0022);
734
ec863ba6
LP
735 if ((r = parse_argv(argc, argv)) <= 0)
736 goto finish;
737
0cf84693
LP
738 if (arg_console) {
739 setsid();
740 release_terminal();
741 }
742
ec863ba6
LP
743 if (arg_action == ACTION_WATCH ||
744 arg_action == ACTION_WALL)
745 r = watch_passwords();
746 else
747 r = show_passwords();
748
96707269
LP
749 if (r < 0)
750 log_error("Error: %s", strerror(-r));
751
ec863ba6
LP
752finish:
753 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
754}