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