]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/tty-ask-password-agent/tty-ask-password-agent.c
treewide: yet more log_*_errno + return simplifications
[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 #include "def.h"
45
46 static enum {
47 ACTION_LIST,
48 ACTION_QUERY,
49 ACTION_WATCH,
50 ACTION_WALL
51 } arg_action = ACTION_QUERY;
52
53 static bool arg_plymouth = false;
54 static bool arg_console = false;
55
56 static int ask_password_plymouth(
57 const char *message,
58 usec_t until,
59 const char *flag_file,
60 bool accept_cached,
61 char ***_passphrases) {
62
63 _cleanup_close_ int fd = -1, notify = -1;
64 union sockaddr_union sa = PLYMOUTH_SOCKET;
65 _cleanup_free_ char *packet = NULL;
66 ssize_t k;
67 int r, n;
68 struct pollfd pollfd[2] = {};
69 char buffer[LINE_MAX];
70 size_t p = 0;
71 enum {
72 POLL_SOCKET,
73 POLL_INOTIFY
74 };
75
76 assert(_passphrases);
77
78 if (flag_file) {
79 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
80 if (notify < 0)
81 return -errno;
82
83 r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
84 if (r < 0)
85 return -errno;
86 }
87
88 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
89 if (fd < 0)
90 return -errno;
91
92 r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1));
93 if (r < 0) {
94 log_error("Failed to connect to Plymouth: %m");
95 return -errno;
96 }
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 k = loop_write(fd, packet, n + 1, true);
109 if (k != n + 1)
110 return k < 0 ? (int) k : -EIO;
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 free(packet);
165 packet = NULL;
166
167 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
168 return -ENOMEM;
169
170 k = loop_write(fd, packet, n+1, true);
171 if (k != n + 1)
172 return k < 0 ? (int) k : -EIO;
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 log_error("socket(): %m");
347 return -errno;
348 }
349
350 sa.un.sun_family = AF_UNIX;
351 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
352
353 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
354 offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
355 if (r < 0) {
356 log_error("Failed to send: %m");
357 return r;
358 }
359 }
360
361 return 0;
362 }
363
364 static int wall_tty_block(void) {
365 _cleanup_free_ char *p = NULL;
366 int fd, r;
367 dev_t devnr;
368
369 r = get_ctty_devnr(0, &devnr);
370 if (r < 0)
371 return r;
372
373 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
374 return -ENOMEM;
375
376 mkdir_parents_label(p, 0700);
377 mkfifo(p, 0600);
378
379 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
380 if (fd < 0)
381 return -errno;
382
383 return fd;
384 }
385
386 static bool wall_tty_match(const char *path) {
387 int fd, r;
388 struct stat st;
389 _cleanup_free_ char *p = NULL;
390
391 if (!path_is_absolute(path))
392 path = strappenda("/dev/", path);
393
394 r = lstat(path, &st);
395 if (r < 0)
396 return true;
397
398 if (!S_ISCHR(st.st_mode))
399 return true;
400
401 /* We use named pipes to ensure that wall messages suggesting
402 * password entry are not printed over password prompts
403 * already shown. We use the fact here that opening a pipe in
404 * non-blocking mode for write-only will succeed only if
405 * there's some writer behind it. Using pipes has the
406 * advantage that the block will automatically go away if the
407 * process dies. */
408
409 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
410 return true;
411
412 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
413 if (fd < 0)
414 return true;
415
416 /* What, we managed to open the pipe? Then this tty is filtered. */
417 safe_close(fd);
418 return false;
419 }
420
421 static int show_passwords(void) {
422 _cleanup_closedir_ DIR *d;
423 struct dirent *de;
424 int r = 0;
425
426 d = opendir("/run/systemd/ask-password");
427 if (!d) {
428 if (errno == ENOENT)
429 return 0;
430
431 log_error("opendir(/run/systemd/ask-password): %m");
432 return -errno;
433 }
434
435 while ((de = readdir(d))) {
436 _cleanup_free_ char *p = NULL, *wall = NULL;
437 int q;
438
439 /* We only support /dev on tmpfs, hence we can rely on
440 * d_type to be reliable */
441
442 if (de->d_type != DT_REG)
443 continue;
444
445 if (ignore_file(de->d_name))
446 continue;
447
448 if (!startswith(de->d_name, "ask."))
449 continue;
450
451 p = strappend("/run/systemd/ask-password/", de->d_name);
452 if (!p)
453 return log_oom();
454
455 q = parse_password(p, &wall);
456 if (q < 0 && r == 0)
457 r = q;
458
459 if (wall)
460 utmp_wall(wall, NULL, wall_tty_match);
461 }
462
463 return r;
464 }
465
466 static int watch_passwords(void) {
467 enum {
468 FD_INOTIFY,
469 FD_SIGNAL,
470 _FD_MAX
471 };
472
473 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
474 struct pollfd pollfd[_FD_MAX] = {};
475 sigset_t mask;
476 int r;
477
478 tty_block_fd = wall_tty_block();
479
480 mkdir_p_label("/run/systemd/ask-password", 0755);
481
482 notify = inotify_init1(IN_CLOEXEC);
483 if (notify < 0)
484 return -errno;
485
486 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
487 return -errno;
488
489 assert_se(sigemptyset(&mask) == 0);
490 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
491 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
492
493 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
494 if (signal_fd < 0)
495 return -errno;
496
497 pollfd[FD_INOTIFY].fd = notify;
498 pollfd[FD_INOTIFY].events = POLLIN;
499 pollfd[FD_SIGNAL].fd = signal_fd;
500 pollfd[FD_SIGNAL].events = POLLIN;
501
502 for (;;) {
503 r = show_passwords();
504 if (r < 0)
505 log_error_errno(r, "Failed to show password: %m");
506
507 if (poll(pollfd, _FD_MAX, -1) < 0) {
508 if (errno == EINTR)
509 continue;
510
511 return -errno;
512 }
513
514 if (pollfd[FD_INOTIFY].revents != 0)
515 flush_fd(notify);
516
517 if (pollfd[FD_SIGNAL].revents != 0)
518 break;
519 }
520
521 return 0;
522 }
523
524 static void help(void) {
525 printf("%s [OPTIONS...]\n\n"
526 "Process system password requests.\n\n"
527 " -h --help Show this help\n"
528 " --version Show package version\n"
529 " --list Show pending password requests\n"
530 " --query Process pending password requests\n"
531 " --watch Continuously process password requests\n"
532 " --wall Continuously forward password requests to wall\n"
533 " --plymouth Ask question with Plymouth instead of on TTY\n"
534 " --console Ask question on /dev/console instead of current TTY\n",
535 program_invocation_short_name);
536 }
537
538 static int parse_argv(int argc, char *argv[]) {
539
540 enum {
541 ARG_LIST = 0x100,
542 ARG_QUERY,
543 ARG_WATCH,
544 ARG_WALL,
545 ARG_PLYMOUTH,
546 ARG_CONSOLE,
547 ARG_VERSION
548 };
549
550 static const struct option options[] = {
551 { "help", no_argument, NULL, 'h' },
552 { "version", no_argument, NULL, ARG_VERSION },
553 { "list", no_argument, NULL, ARG_LIST },
554 { "query", no_argument, NULL, ARG_QUERY },
555 { "watch", no_argument, NULL, ARG_WATCH },
556 { "wall", no_argument, NULL, ARG_WALL },
557 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
558 { "console", no_argument, NULL, ARG_CONSOLE },
559 {}
560 };
561
562 int c;
563
564 assert(argc >= 0);
565 assert(argv);
566
567 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
568
569 switch (c) {
570
571 case 'h':
572 help();
573 return 0;
574
575 case ARG_VERSION:
576 puts(PACKAGE_STRING);
577 puts(SYSTEMD_FEATURES);
578 return 0;
579
580 case ARG_LIST:
581 arg_action = ACTION_LIST;
582 break;
583
584 case ARG_QUERY:
585 arg_action = ACTION_QUERY;
586 break;
587
588 case ARG_WATCH:
589 arg_action = ACTION_WATCH;
590 break;
591
592 case ARG_WALL:
593 arg_action = ACTION_WALL;
594 break;
595
596 case ARG_PLYMOUTH:
597 arg_plymouth = true;
598 break;
599
600 case ARG_CONSOLE:
601 arg_console = true;
602 break;
603
604 case '?':
605 return -EINVAL;
606
607 default:
608 assert_not_reached("Unhandled option");
609 }
610
611 if (optind != argc) {
612 log_error("%s takes no arguments.", program_invocation_short_name);
613 return -EINVAL;
614 }
615
616 return 1;
617 }
618
619 int main(int argc, char *argv[]) {
620 int r;
621
622 log_set_target(LOG_TARGET_AUTO);
623 log_parse_environment();
624 log_open();
625
626 umask(0022);
627
628 r = parse_argv(argc, argv);
629 if (r <= 0)
630 goto finish;
631
632 if (arg_console) {
633 setsid();
634 release_terminal();
635 }
636
637 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
638 r = watch_passwords();
639 else
640 r = show_passwords();
641
642 if (r < 0)
643 log_error_errno(r, "Error: %m");
644
645 finish:
646 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
647 }