]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/tty-ask-password-agent/tty-ask-password-agent.c
Constify ConfigTableItem tables
[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 sa.sa.sa_family = AF_UNIX;
95 strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
96 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
97 log_error("Failed to connect to Plymouth: %m");
98 r = -errno;
99 goto finish;
100 }
101
102 if (accept_cached) {
103 packet = strdup("c");
104 n = 1;
105 } else
106 asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n);
107
108 if (!packet) {
109 r = -ENOMEM;
110 goto finish;
111 }
112
113 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
114 r = k < 0 ? (int) k : -EIO;
115 goto finish;
116 }
117
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 safe_close(notify);
238 safe_close(fd);
239
240 free(packet);
241
242 return r;
243 }
244
245 static int parse_password(const char *filename, char **wall) {
246 char *socket_name = NULL, *message = NULL, *packet = NULL;
247 uint64_t not_after = 0;
248 unsigned pid = 0;
249 int socket_fd = -1;
250 bool accept_cached = false;
251
252 const ConfigTableItem items[] = {
253 { "Ask", "Socket", config_parse_string, 0, &socket_name },
254 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
255 { "Ask", "Message", config_parse_string, 0, &message },
256 { "Ask", "PID", config_parse_unsigned, 0, &pid },
257 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
258 { NULL, NULL, NULL, 0, NULL }
259 };
260
261 FILE *f;
262 int r;
263
264 assert(filename);
265
266 f = fopen(filename, "re");
267 if (!f) {
268 if (errno == ENOENT)
269 return 0;
270
271 log_error("open(%s): %m", filename);
272 return -errno;
273 }
274
275 r = config_parse(NULL, filename, f, NULL, config_item_table_lookup, items, true, false, NULL);
276 if (r < 0) {
277 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
278 goto finish;
279 }
280
281 if (!socket_name) {
282 log_error("Invalid password file %s", filename);
283 r = -EBADMSG;
284 goto finish;
285 }
286
287 if (not_after > 0) {
288 if (now(CLOCK_MONOTONIC) > not_after) {
289 r = 0;
290 goto finish;
291 }
292 }
293
294 if (pid > 0 && !pid_is_alive(pid)) {
295 r = 0;
296 goto finish;
297 }
298
299 if (arg_action == ACTION_LIST)
300 printf("'%s' (PID %u)\n", message, pid);
301 else if (arg_action == ACTION_WALL) {
302 char *_wall;
303
304 if (asprintf(&_wall,
305 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
306 "Please enter password with the systemd-tty-ask-password-agent tool!",
307 *wall ? *wall : "",
308 *wall ? "\r\n\r\n" : "",
309 message,
310 pid) < 0) {
311 r = log_oom();
312 goto finish;
313 }
314
315 free(*wall);
316 *wall = _wall;
317 } else {
318 union {
319 struct sockaddr sa;
320 struct sockaddr_un un;
321 } sa = {};
322 size_t packet_length = 0;
323
324 assert(arg_action == ACTION_QUERY ||
325 arg_action == ACTION_WATCH);
326
327 if (access(socket_name, W_OK) < 0) {
328
329 if (arg_action == ACTION_QUERY)
330 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
331
332 r = 0;
333 goto finish;
334 }
335
336 if (arg_plymouth) {
337 _cleanup_strv_free_ char **passwords = NULL;
338
339 if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
340 char **p;
341
342 packet_length = 1;
343 STRV_FOREACH(p, passwords)
344 packet_length += strlen(*p) + 1;
345
346 if (!(packet = new(char, packet_length)))
347 r = -ENOMEM;
348 else {
349 char *d;
350
351 packet[0] = '+';
352 d = packet+1;
353
354 STRV_FOREACH(p, passwords)
355 d = stpcpy(d, *p) + 1;
356 }
357 }
358
359 } else {
360 int tty_fd = -1;
361 char *password = NULL;
362
363 if (arg_console)
364 if ((tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1)) < 0) {
365 r = tty_fd;
366 goto finish;
367 }
368
369 r = ask_password_tty(message, not_after, filename, &password);
370
371 if (arg_console) {
372 safe_close(tty_fd);
373 release_terminal();
374 }
375
376 if (r >= 0) {
377 packet_length = 1+strlen(password)+1;
378 if (!(packet = new(char, packet_length)))
379 r = -ENOMEM;
380 else {
381 packet[0] = '+';
382 strcpy(packet+1, password);
383 }
384
385 free(password);
386 }
387 }
388
389 if (r == -ETIME || r == -ENOENT) {
390 /* If the query went away, that's OK */
391 r = 0;
392 goto finish;
393 }
394
395 if (r < 0) {
396 log_error("Failed to query password: %s", strerror(-r));
397 goto finish;
398 }
399
400 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
401 log_error("socket(): %m");
402 r = -errno;
403 goto finish;
404 }
405
406 sa.un.sun_family = AF_UNIX;
407 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
408
409 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
410 log_error("Failed to send: %m");
411 r = -errno;
412 goto finish;
413 }
414 }
415
416 finish:
417 fclose(f);
418
419 safe_close(socket_fd);
420
421 free(packet);
422 free(socket_name);
423 free(message);
424
425 return r;
426 }
427
428 static int wall_tty_block(void) {
429 char *p;
430 int fd, r;
431 dev_t devnr;
432
433 r = get_ctty_devnr(0, &devnr);
434 if (r < 0)
435 return r;
436
437 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
438 return -ENOMEM;
439
440 mkdir_parents_label(p, 0700);
441 mkfifo(p, 0600);
442
443 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
444 free(p);
445
446 if (fd < 0)
447 return -errno;
448
449 return fd;
450 }
451
452 static bool wall_tty_match(const char *path) {
453 int fd, k;
454 char *p;
455 struct stat st;
456
457 if (path_is_absolute(path))
458 k = lstat(path, &st);
459 else {
460 if (asprintf(&p, "/dev/%s", path) < 0)
461 return true;
462
463 k = lstat(p, &st);
464 free(p);
465 }
466
467 if (k < 0)
468 return true;
469
470 if (!S_ISCHR(st.st_mode))
471 return true;
472
473 /* We use named pipes to ensure that wall messages suggesting
474 * password entry are not printed over password prompts
475 * already shown. We use the fact here that opening a pipe in
476 * non-blocking mode for write-only will succeed only if
477 * there's some writer behind it. Using pipes has the
478 * advantage that the block will automatically go away if the
479 * process dies. */
480
481 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
482 return true;
483
484 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
485 free(p);
486
487 if (fd < 0)
488 return true;
489
490 /* What, we managed to open the pipe? Then this tty is filtered. */
491 safe_close(fd);
492 return false;
493 }
494
495 static int show_passwords(void) {
496 DIR *d;
497 struct dirent *de;
498 int r = 0;
499
500 if (!(d = opendir("/run/systemd/ask-password"))) {
501 if (errno == ENOENT)
502 return 0;
503
504 log_error("opendir(/run/systemd/ask-password): %m");
505 return -errno;
506 }
507
508 while ((de = readdir(d))) {
509 char *p;
510 int q;
511 char *wall;
512
513 /* We only support /dev on tmpfs, hence we can rely on
514 * d_type to be reliable */
515
516 if (de->d_type != DT_REG)
517 continue;
518
519 if (ignore_file(de->d_name))
520 continue;
521
522 if (!startswith(de->d_name, "ask."))
523 continue;
524
525 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
526 r = log_oom();
527 goto finish;
528 }
529
530 wall = NULL;
531 if ((q = parse_password(p, &wall)) < 0)
532 r = q;
533
534 free(p);
535
536 if (wall) {
537 utmp_wall(wall, NULL, wall_tty_match);
538 free(wall);
539 }
540 }
541
542 finish:
543 if (d)
544 closedir(d);
545
546 return r;
547 }
548
549 static int watch_passwords(void) {
550 enum {
551 FD_INOTIFY,
552 FD_SIGNAL,
553 _FD_MAX
554 };
555
556 int notify = -1, signal_fd = -1, tty_block_fd = -1;
557 struct pollfd pollfd[_FD_MAX] = {};
558 sigset_t mask;
559 int r;
560
561 tty_block_fd = wall_tty_block();
562
563 mkdir_p_label("/run/systemd/ask-password", 0755);
564
565 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
566 r = -errno;
567 goto finish;
568 }
569
570 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
571 r = -errno;
572 goto finish;
573 }
574
575 assert_se(sigemptyset(&mask) == 0);
576 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
577 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
578
579 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
580 log_error("signalfd(): %m");
581 r = -errno;
582 goto finish;
583 }
584
585 pollfd[FD_INOTIFY].fd = notify;
586 pollfd[FD_INOTIFY].events = POLLIN;
587 pollfd[FD_SIGNAL].fd = signal_fd;
588 pollfd[FD_SIGNAL].events = POLLIN;
589
590 for (;;) {
591 if ((r = show_passwords()) < 0)
592 log_error("Failed to show password: %s", strerror(-r));
593
594 if (poll(pollfd, _FD_MAX, -1) < 0) {
595
596 if (errno == EINTR)
597 continue;
598
599 r = -errno;
600 goto finish;
601 }
602
603 if (pollfd[FD_INOTIFY].revents != 0)
604 flush_fd(notify);
605
606 if (pollfd[FD_SIGNAL].revents != 0)
607 break;
608 }
609
610 r = 0;
611
612 finish:
613 safe_close(notify);
614 safe_close(signal_fd);
615 safe_close(tty_block_fd);
616
617 return r;
618 }
619
620 static int help(void) {
621
622 printf("%s [OPTIONS...]\n\n"
623 "Process system password requests.\n\n"
624 " -h --help Show this help\n"
625 " --version Show package version\n"
626 " --list Show pending password requests\n"
627 " --query Process pending password requests\n"
628 " --watch Continuously process password requests\n"
629 " --wall Continuously forward password requests to wall\n"
630 " --plymouth Ask question with Plymouth instead of on TTY\n"
631 " --console Ask question on /dev/console instead of current TTY\n",
632 program_invocation_short_name);
633
634 return 0;
635 }
636
637 static int parse_argv(int argc, char *argv[]) {
638
639 enum {
640 ARG_LIST = 0x100,
641 ARG_QUERY,
642 ARG_WATCH,
643 ARG_WALL,
644 ARG_PLYMOUTH,
645 ARG_CONSOLE,
646 ARG_VERSION
647 };
648
649 static const struct option options[] = {
650 { "help", no_argument, NULL, 'h' },
651 { "version", no_argument, NULL, ARG_VERSION },
652 { "list", no_argument, NULL, ARG_LIST },
653 { "query", no_argument, NULL, ARG_QUERY },
654 { "watch", no_argument, NULL, ARG_WATCH },
655 { "wall", no_argument, NULL, ARG_WALL },
656 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
657 { "console", no_argument, NULL, ARG_CONSOLE },
658 {}
659 };
660
661 int c;
662
663 assert(argc >= 0);
664 assert(argv);
665
666 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
667
668 switch (c) {
669
670 case 'h':
671 return help();
672
673 case ARG_VERSION:
674 puts(PACKAGE_STRING);
675 puts(SYSTEMD_FEATURES);
676 return 0;
677
678 case ARG_LIST:
679 arg_action = ACTION_LIST;
680 break;
681
682 case ARG_QUERY:
683 arg_action = ACTION_QUERY;
684 break;
685
686 case ARG_WATCH:
687 arg_action = ACTION_WATCH;
688 break;
689
690 case ARG_WALL:
691 arg_action = ACTION_WALL;
692 break;
693
694 case ARG_PLYMOUTH:
695 arg_plymouth = true;
696 break;
697
698 case ARG_CONSOLE:
699 arg_console = true;
700 break;
701
702 case '?':
703 return -EINVAL;
704
705 default:
706 assert_not_reached("Unhandled option");
707 }
708 }
709
710 if (optind != argc) {
711 help();
712 return -EINVAL;
713 }
714
715 return 1;
716 }
717
718 int main(int argc, char *argv[]) {
719 int r;
720
721 log_set_target(LOG_TARGET_AUTO);
722 log_parse_environment();
723 log_open();
724
725 umask(0022);
726
727 if ((r = parse_argv(argc, argv)) <= 0)
728 goto finish;
729
730 if (arg_console) {
731 setsid();
732 release_terminal();
733 }
734
735 if (arg_action == ACTION_WATCH ||
736 arg_action == ACTION_WALL)
737 r = watch_passwords();
738 else
739 r = show_passwords();
740
741 if (r < 0)
742 log_error("Error: %s", strerror(-r));
743
744 finish:
745 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
746 }