]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/tty-ask-password-agent/tty-ask-password-agent.c
build-sys: drop all distribution specfic checks
[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 r = log_oom();
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 r = log_oom();
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_label("/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 " --version Show package version\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 ARG_VERSION
662 };
663
664 static const struct option options[] = {
665 { "help", no_argument, NULL, 'h' },
666 { "version", no_argument, NULL, ARG_VERSION },
667 { "list", no_argument, NULL, ARG_LIST },
668 { "query", no_argument, NULL, ARG_QUERY },
669 { "watch", no_argument, NULL, ARG_WATCH },
670 { "wall", no_argument, NULL, ARG_WALL },
671 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
672 { "console", no_argument, NULL, ARG_CONSOLE },
673 { NULL, 0, NULL, 0 }
674 };
675
676 int c;
677
678 assert(argc >= 0);
679 assert(argv);
680
681 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
682
683 switch (c) {
684
685 case 'h':
686 help();
687 return 0;
688
689 case ARG_VERSION:
690 puts(PACKAGE_STRING);
691 puts(SYSTEMD_FEATURES);
692 return 0;
693
694 case ARG_LIST:
695 arg_action = ACTION_LIST;
696 break;
697
698 case ARG_QUERY:
699 arg_action = ACTION_QUERY;
700 break;
701
702 case ARG_WATCH:
703 arg_action = ACTION_WATCH;
704 break;
705
706 case ARG_WALL:
707 arg_action = ACTION_WALL;
708 break;
709
710 case ARG_PLYMOUTH:
711 arg_plymouth = true;
712 break;
713
714 case ARG_CONSOLE:
715 arg_console = true;
716 break;
717
718 case '?':
719 return -EINVAL;
720
721 default:
722 log_error("Unknown option code %c", c);
723 return -EINVAL;
724 }
725 }
726
727 if (optind != argc) {
728 help();
729 return -EINVAL;
730 }
731
732 return 1;
733 }
734
735 int main(int argc, char *argv[]) {
736 int r;
737
738 log_set_target(LOG_TARGET_AUTO);
739 log_parse_environment();
740 log_open();
741
742 umask(0022);
743
744 if ((r = parse_argv(argc, argv)) <= 0)
745 goto finish;
746
747 if (arg_console) {
748 setsid();
749 release_terminal();
750 }
751
752 if (arg_action == ACTION_WATCH ||
753 arg_action == ACTION_WALL)
754 r = watch_passwords();
755 else
756 r = show_passwords();
757
758 if (r < 0)
759 log_error("Error: %s", strerror(-r));
760
761 finish:
762 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
763 }