]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/tty-ask-password-agent.c
rename basic.la to shared.la and put selinux deps in shared-selinx.la
[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 General Public License as published by
10 the Free Software Foundation; either version 2 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 General Public License for more details.
17
18 You should have received a copy of the GNU 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 }