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