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