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